HORN-32: Support RNN
diff --git a/semi.seq b/semi.seq
new file mode 100644
index 0000000..64e5871
--- /dev/null
+++ b/semi.seq
Binary files differ
diff --git a/src/main/java/org/apache/horn/examples/DropoutNeuron.java b/src/main/java/org/apache/horn/core/DropoutNeuron.java
similarity index 95%
rename from src/main/java/org/apache/horn/examples/DropoutNeuron.java
rename to src/main/java/org/apache/horn/core/DropoutNeuron.java
index ec02570..c9ebe9f 100644
--- a/src/main/java/org/apache/horn/examples/DropoutNeuron.java
+++ b/src/main/java/org/apache/horn/core/DropoutNeuron.java
@@ -15,13 +15,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.horn.examples;
+package org.apache.horn.core;
 
 import java.io.IOException;
 
 import org.apache.hadoop.io.FloatWritable;
-import org.apache.horn.core.Neuron;
-import org.apache.horn.core.Synapse;
 import org.apache.horn.utils.MathUtils;
 
 public class DropoutNeuron extends
diff --git a/src/main/java/org/apache/horn/core/HornJob.java b/src/main/java/org/apache/horn/core/HornJob.java
index 82343fe..ea622ff 100644
--- a/src/main/java/org/apache/horn/core/HornJob.java
+++ b/src/main/java/org/apache/horn/core/HornJob.java
@@ -62,6 +62,11 @@
     neuralNetwork.setDropRateOfInputLayer(dropRate);
   }
 
+  public void inputLayer(int featureDimension, float dropRate, Class<? extends Neuron<?>> neuronClass) {
+    addLayer(featureDimension, null, neuronClass);
+    neuralNetwork.setDropRateOfInputLayer(dropRate);
+  }
+
   public void addLayer(int featureDimension, Class<? extends Function> func,
       Class<? extends Neuron<?>> neuronClass) {
     neuralNetwork.addLayer(
@@ -71,12 +76,37 @@
             .getSimpleName()) : null, neuronClass);
   }
 
+  /**
+   * TODO: Adds comments
+   * @param featureDimension
+   * @param class1
+   * @param neuronClass
+   */
+  public void addLayer(int featureDimension, Class<? extends Function> func,
+      Class<? extends Neuron<?>> neuronClass, boolean isRecurrent) {
+    if (neuralNetwork instanceof RecurrentLayeredNeuralNetwork) {
+        ((RecurrentLayeredNeuralNetwork)neuralNetwork).addLayer(
+            featureDimension,
+            false,
+            (func != null) ? FunctionFactory.createFloatFunction(func
+                .getSimpleName()) : null, neuronClass, null, isRecurrent);
+    } else {
+      this.addLayer(featureDimension, func, neuronClass);
+    }
+  }
+
   public void outputLayer(int labels, Class<? extends Function> func,
       Class<? extends Neuron<?>> neuronClass) {
     neuralNetwork.addLayer(labels, true,
         FunctionFactory.createFloatFunction(func.getSimpleName()), neuronClass);
   }
 
+  public void outputLayer(int labels, Class<? extends Function> func,
+      Class<? extends Neuron<?>> neuronClass, int numOutCells) {
+    ((RecurrentLayeredNeuralNetwork)neuralNetwork).addLayer(labels, true,
+        FunctionFactory.createFloatFunction(func.getSimpleName()), neuronClass, numOutCells);
+  }
+
   public void setCostFunction(Class<? extends Function> func) {
     neuralNetwork.setCostFunction(FunctionFactory.createFloatFloatFunction(func
         .getSimpleName()));
@@ -94,6 +124,11 @@
     this.conf.setInt("training.batch.size", batchSize);
   }
 
+  public void setRecurrentStepSize(int stepSize) {
+    ((RecurrentLayeredNeuralNetwork) neuralNetwork).setRecurrentStepSize(stepSize);
+    this.conf.setInt("training.recurrent.step.size", stepSize);
+  }
+
   public void setTrainingMethod(TrainingMethod method) {
     this.neuralNetwork.setTrainingMethod(method);
   }
diff --git a/src/main/java/org/apache/horn/examples/DropoutNeuron.java b/src/main/java/org/apache/horn/core/RecurrentDropoutNeuron.java
similarity index 70%
copy from src/main/java/org/apache/horn/examples/DropoutNeuron.java
copy to src/main/java/org/apache/horn/core/RecurrentDropoutNeuron.java
index ec02570..da4846b 100644
--- a/src/main/java/org/apache/horn/examples/DropoutNeuron.java
+++ b/src/main/java/org/apache/horn/core/RecurrentDropoutNeuron.java
@@ -15,24 +15,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.horn.examples;
+package org.apache.horn.core;
 
 import java.io.IOException;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.io.FloatWritable;
-import org.apache.horn.core.Neuron;
-import org.apache.horn.core.Synapse;
 import org.apache.horn.utils.MathUtils;
 
-public class DropoutNeuron extends
+public class RecurrentDropoutNeuron extends
     Neuron<Synapse<FloatWritable, FloatWritable>> {
 
   private float m2;
+  private float recurrentDelta = 0;
+  private double dropRate = 0;
+  
+  public double getDropRate() {
+    return dropRate;
+  }
+
+  public void setDropRate(double dropRate) {
+    this.dropRate = dropRate;
+  }
 
   @Override
   public void forward(Iterable<Synapse<FloatWritable, FloatWritable>> messages)
       throws IOException {
-    m2 = (isTraining()) ? MathUtils.getBinomial(1, 0.5) : 0.5f;
+    m2 = (isTraining()) ? MathUtils.getBinomial(1, dropRate) :1.0f;
 
     if (m2 > 0) {
       float sum = 0;
@@ -48,6 +58,8 @@
     }
   }
 
+  private static final Log LOG = LogFactory.getLog(RecurrentDropoutNeuron.class);
+  
   @Override
   public void backward(Iterable<Synapse<FloatWritable, FloatWritable>> messages)
       throws IOException {
@@ -63,11 +75,18 @@
             * this.getOutput() + this.getMomentumWeight() * m.getPrevWeight();
         this.push(weight);
       }
-
-      this.backpropagate(delta * squashingFunction.applyDerivative(getOutput()));
+      // TODO set squashingFunction of recurrent neurons identity
+      this.backpropagate(recurrentDelta + delta * squashingFunction.applyDerivative(getOutput()));
     } else {
       this.backpropagate(0);
     }
   }
 
+  public float getRecurrentDelta() {
+    return recurrentDelta;
+  }
+
+  public void setRecurrentDelta(float recurrentDelta) {
+    this.recurrentDelta = recurrentDelta;
+  }
 }
diff --git a/src/main/java/org/apache/horn/core/RecurrentLayeredNeuralNetwork.java b/src/main/java/org/apache/horn/core/RecurrentLayeredNeuralNetwork.java
new file mode 100644
index 0000000..d65e4a6
--- /dev/null
+++ b/src/main/java/org/apache/horn/core/RecurrentLayeredNeuralNetwork.java
@@ -0,0 +1,1032 @@
+/**
+ * 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.
+ */
+package org.apache.horn.core;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.math.RandomUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.io.FloatWritable;
+import org.apache.hadoop.io.LongWritable;
+import org.apache.hadoop.io.NullWritable;
+import org.apache.hadoop.io.WritableUtils;
+import org.apache.hama.Constants;
+import org.apache.hama.HamaConfiguration;
+import org.apache.hama.bsp.BSPJob;
+import org.apache.hama.commons.io.FloatMatrixWritable;
+import org.apache.hama.commons.io.VectorWritable;
+import org.apache.hama.commons.math.DenseFloatMatrix;
+import org.apache.hama.commons.math.DenseFloatVector;
+import org.apache.hama.commons.math.FloatFunction;
+import org.apache.hama.commons.math.FloatMatrix;
+import org.apache.hama.commons.math.FloatVector;
+import org.apache.hama.util.ReflectionUtils;
+import org.apache.horn.core.Constants.LearningStyle;
+import org.apache.horn.core.Constants.TrainingMethod;
+import org.apache.horn.examples.MultiLayerPerceptron.StandardNeuron;
+import org.apache.horn.funcs.FunctionFactory;
+import org.apache.horn.funcs.IdentityFunction;
+import org.apache.horn.funcs.SoftMax;
+import org.apache.horn.utils.MathUtils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+/**
+ * SmallLayeredNeuralNetwork defines the general operations for derivative
+ * layered models, include Linear Regression, Logistic Regression, Multilayer
+ * Perceptron, Autoencoder, and Restricted Boltzmann Machine, etc. For
+ * SmallLayeredNeuralNetwork, the training can be conducted in parallel, but the
+ * parameters of the models are assumes to be stored in a single machine.
+ * 
+ * In general, these models consist of neurons which are aligned in layers.
+ * Between layers, for any two adjacent layers, the neurons are connected to
+ * form a bipartite weighted graph.
+ * 
+ */
+public class RecurrentLayeredNeuralNetwork extends AbstractLayeredNeuralNetwork {
+
+  private static final Log LOG = LogFactory.getLog(RecurrentLayeredNeuralNetwork.class);
+
+  /* Weights between neurons at adjacent layers */
+  protected List<FloatMatrix> weightMatrixList;
+  /* Weights between neurons at adjacent layers */
+  protected List<List<FloatMatrix>> weightMatrixLists;
+  /* Previous weight updates between neurons at adjacent layers */
+  protected List<FloatMatrix> prevWeightUpdatesList;
+  protected List<List<FloatMatrix>> prevWeightUpdatesLists;
+  /* Different layers can have different squashing function */
+  protected List<FloatFunction> squashingFunctionList;
+  protected List<Class<? extends Neuron<?>>> neuronClassList;
+  /* Record the recurrent layer */
+  protected List<Boolean> recurrentLayerList;
+  /* Recurrent step size */
+  protected int recurrentStepSize;
+  protected int finalLayerIdx;
+  private List<Neuron<?>[]> neurons;
+  private List<List<Neuron<?>[]>> neuronLists;
+  private float dropRate;
+  private long iterations;
+
+  private int numOutCells;
+
+  public RecurrentLayeredNeuralNetwork() {
+    this.layerSizeList = Lists.newArrayList();
+    this.weightMatrixList = Lists.newArrayList();
+    this.prevWeightUpdatesList = Lists.newArrayList();
+    this.squashingFunctionList = Lists.newArrayList();
+    this.neuronClassList = Lists.newArrayList();
+    this.weightMatrixLists = Lists.newArrayList();
+    this.prevWeightUpdatesLists = Lists.newArrayList();
+    this.neuronLists = Lists.newArrayList();
+    this.recurrentLayerList = Lists.newArrayList();
+  }
+
+  public RecurrentLayeredNeuralNetwork(HamaConfiguration conf, String modelPath) {
+    super(conf, modelPath);
+    initializeNeurons(false);
+    initializeWeightMatrixLists();
+  }
+
+  public RecurrentLayeredNeuralNetwork(HamaConfiguration conf, String modelPath,
+      boolean isTraining) {
+    super(conf, modelPath);
+    initializeNeurons(isTraining);
+    initializeWeightMatrixLists();
+  }
+
+  /**
+   *  initialize neuron objects
+   * @param isTraining
+   */
+  private void initializeNeurons(boolean isTraining) {
+    this.neuronLists = Lists.newArrayListWithExpectedSize(recurrentStepSize);
+    for (int stepIdx = 0; stepIdx < this.recurrentStepSize; stepIdx++) {
+      neurons = new ArrayList<Neuron<?>[]>();
+      
+      int expectedNeuronsSize = this.layerSizeList.size();
+      if (stepIdx < this.recurrentStepSize - this.numOutCells) {
+        expectedNeuronsSize--;
+      }
+      for (int neuronLayerIdx = 0; neuronLayerIdx < expectedNeuronsSize; neuronLayerIdx++) {
+        int numOfNeurons = layerSizeList.get(neuronLayerIdx);
+        // if not final layer and next layer is recurrent
+        if (stepIdx > 0 && neuronLayerIdx < layerSizeList.size() - 1 
+            &&  this.recurrentLayerList.get(neuronLayerIdx+1)) {
+          numOfNeurons = numOfNeurons + layerSizeList.get(neuronLayerIdx+1) - 1;
+        }
+        Class<? extends Neuron<?>> neuronClass;
+        if (neuronLayerIdx == 0)
+          neuronClass = StandardNeuron.class; // actually doesn't needed
+        else
+          neuronClass = neuronClassList.get(neuronLayerIdx - 1);
+
+        Neuron<?>[] tmp = new Neuron[numOfNeurons];
+        for (int neuronIdx = 0; neuronIdx < numOfNeurons; neuronIdx++) {
+          Neuron<?> n = newNeuronInstance(neuronClass);
+          if (n instanceof RecurrentDropoutNeuron)
+            ((RecurrentDropoutNeuron) n).setDropRate(dropRate);
+          if (neuronLayerIdx > 0 && neuronIdx < layerSizeList.get(neuronLayerIdx))
+            n.setSquashingFunction(squashingFunctionList.get(neuronLayerIdx - 1));
+          else
+            n.setSquashingFunction(new IdentityFunction());
+          n.setLayerIndex(neuronLayerIdx);
+          n.setNeuronID(neuronIdx);
+          n.setLearningRate(this.learningRate);
+          n.setMomentumWeight(this.momentumWeight);
+          n.setTraining(isTraining);
+          tmp[neuronIdx] = n;
+        }
+        neurons.add(tmp);
+      }
+      this.neuronLists.add(neurons);
+    }
+  }
+
+  /**
+   * Initialize WeightMatrixLists
+   */
+  public void initializeWeightMatrixLists() {
+    this.numOutCells = (numOutCells == 0 ? this.recurrentStepSize:numOutCells);
+    this.weightMatrixLists.clear();
+    this.weightMatrixLists = Lists.newArrayListWithExpectedSize(this.recurrentStepSize);
+    this.prevWeightUpdatesLists.clear();
+    this.prevWeightUpdatesLists = Lists.newArrayListWithExpectedSize(this.recurrentStepSize);
+
+    for (int stepIdx = 0; stepIdx < recurrentStepSize - 1; stepIdx++) {
+      int expectedMatrixListSize = this.layerSizeList.size() - 1;
+      if (stepIdx < this.recurrentStepSize - this.numOutCells) {
+        expectedMatrixListSize--;
+      }
+      List<FloatMatrix> aWeightMatrixList = Lists.newArrayListWithExpectedSize(
+          expectedMatrixListSize);
+      List<FloatMatrix> aPrevWeightUpdatesList = Lists.newArrayListWithExpectedSize(
+          expectedMatrixListSize);
+      for (int matrixIdx = 0; matrixIdx < expectedMatrixListSize; matrixIdx++) {
+        int rows = this.weightMatrixList.get(matrixIdx).getRowCount();
+        int cols = this.weightMatrixList.get(matrixIdx).getColumnCount();
+        if ( stepIdx == 0 )
+          cols = this.layerSizeList.get(matrixIdx);
+        FloatMatrix weightMatrix = new DenseFloatMatrix(rows, cols);
+        weightMatrix.applyToElements(new FloatFunction() {
+          @Override
+          public float apply(float value) {
+            return RandomUtils.nextFloat() - 0.5f;
+          }
+          @Override
+          public float applyDerivative(float value) {
+            throw new UnsupportedOperationException("");
+          }
+        });
+        aWeightMatrixList.add(weightMatrix);
+        aPrevWeightUpdatesList.add(
+            new DenseFloatMatrix(
+                this.prevWeightUpdatesList.get(matrixIdx).getRowCount(),
+                this.prevWeightUpdatesList.get(matrixIdx).getColumnCount()));
+      }
+      this.weightMatrixLists.add(aWeightMatrixList);
+      this.prevWeightUpdatesLists.add(aPrevWeightUpdatesList);
+    }
+    // add matrix of last step
+    this.weightMatrixLists.add(this.weightMatrixList);
+    this.prevWeightUpdatesLists.add(this.prevWeightUpdatesList);
+    this.weightMatrixList = Lists.newArrayList();
+    this.prevWeightUpdatesList = Lists.newArrayList();
+  }
+
+  @Override
+  /**
+   * {@inheritDoc}
+   */
+  public int addLayer(int size, boolean isFinalLayer,
+      FloatFunction squashingFunction, Class<? extends Neuron<?>> neuronClass) {
+    return addLayer(size, isFinalLayer, squashingFunction, neuronClass, null, true);
+  }
+
+  public int addLayer(int size, boolean isFinalLayer,
+      FloatFunction squashingFunction, Class<? extends Neuron<?>> neuronClass, int numOutCells) {
+    if (isFinalLayer)
+      this.numOutCells = (numOutCells == 0 ? this.recurrentStepSize:numOutCells);
+    return addLayer(size, isFinalLayer, squashingFunction, neuronClass, null, false);
+  }
+
+  public int addLayer(int size, boolean isFinalLayer,
+      FloatFunction squashingFunction, Class<? extends Neuron<?>> neuronClass,
+      Class<? extends IntermediateOutput> interlayer, boolean isRecurrent) {
+    Preconditions.checkArgument(size > 0,
+        "Size of layer must be larger than 0.");
+    if (!isFinalLayer) {
+      if (this.layerSizeList.size() == 0) {
+        this.recurrentLayerList.add(false);
+        LOG.info("add input layer: " + size + " neurons");
+      } else {
+        this.recurrentLayerList.add(isRecurrent);
+        LOG.info("add hidden layer: " + size + " neurons");
+      }
+      size += 1;
+    } else {
+      this.recurrentLayerList.add(false);
+    }
+
+    this.layerSizeList.add(size);
+    int layerIdx = this.layerSizeList.size() - 1;
+    if (isFinalLayer) {
+      this.finalLayerIdx = layerIdx;
+      LOG.info("add output layer: " + size + " neurons");
+    }
+
+    // add weights between current layer and previous layer, and input layer has
+    // no squashing function
+    if (layerIdx > 0) {
+      int sizePrevLayer = this.layerSizeList.get(layerIdx - 1);
+      // row count equals to size of current size and column count equals to
+      // size of previous layer
+      int row = isFinalLayer ? size : size - 1;
+      // expand matrix for recurrent layer
+      int col = !(this.recurrentLayerList.get(layerIdx)) ?
+          sizePrevLayer : sizePrevLayer + this.layerSizeList.get(layerIdx) - 1;
+
+      FloatMatrix weightMatrix = new DenseFloatMatrix(row, col);
+      // initialize weights
+      weightMatrix.applyToElements(new FloatFunction() {
+        @Override
+        public float apply(float value) {
+          return RandomUtils.nextFloat() - 0.5f;
+        }
+
+        @Override
+        public float applyDerivative(float value) {
+          throw new UnsupportedOperationException("");
+        }
+      });
+      this.weightMatrixList.add(weightMatrix);
+      this.prevWeightUpdatesList.add(new DenseFloatMatrix(row, col));
+      this.squashingFunctionList.add(squashingFunction);
+
+      this.neuronClassList.add(neuronClass);
+    }
+    return layerIdx;
+  }
+
+  /**
+   * Update the weight matrices with given matrices.
+   * 
+   * @param matrices
+   */
+  public void updateWeightMatrices(FloatMatrix[] matrices) {
+    int matrixIdx = 0;
+    for (List<FloatMatrix> aWeightMatrixList: this.weightMatrixLists) {
+      for (int weightMatrixIdx = 0; weightMatrixIdx < aWeightMatrixList.size(); weightMatrixIdx++) {
+        FloatMatrix matrix = aWeightMatrixList.get(weightMatrixIdx);
+        aWeightMatrixList.set(weightMatrixIdx, matrix.add(matrices[matrixIdx++]));
+      }
+    }
+  }
+
+  /**
+   * Set the previous weight matrices.
+   * 
+   * @param prevUpdates
+   */
+  void setPrevWeightMatrices(FloatMatrix[] prevUpdates) {
+    int matrixIdx = 0;
+    for (List<FloatMatrix> aWeightUpdateMatrixList: this.prevWeightUpdatesLists) {
+      for (int weightMatrixIdx = 0; weightMatrixIdx < aWeightUpdateMatrixList.size();
+          weightMatrixIdx++) {
+        aWeightUpdateMatrixList.set(weightMatrixIdx, prevUpdates[matrixIdx++]);
+      }
+    }
+  }
+
+  /**
+   * Add a batch of matrices onto the given destination matrices.
+   * 
+   * @param destMatrices
+   * @param sourceMatrices
+   */
+  static void matricesAdd(FloatMatrix[] destMatrices,
+      FloatMatrix[] sourceMatrices) {
+    for (int i = 0; i < destMatrices.length; ++i) {
+      destMatrices[i] = destMatrices[i].add(sourceMatrices[i]);
+    }
+  }
+
+  /**
+   * Get all the weight matrices.
+   * 
+   * @return The matrices in form of matrix array.
+   */
+  FloatMatrix[] getWeightMatrices() {
+    FloatMatrix[] matrices = new FloatMatrix[this.getSizeOfWeightmatrix()];
+    int matrixIdx = 0;
+    for (List<FloatMatrix> aWeightMatrixList: this.weightMatrixLists) {
+      for (FloatMatrix aWeightMatrix : aWeightMatrixList) {
+        matrices[matrixIdx++] = aWeightMatrix;
+      }
+    }
+    return matrices;
+  }
+
+  /**
+   * Set the weight matrices.
+   * 
+   * @param matrices
+   */
+  public void setWeightMatrices(FloatMatrix[] matrices) {
+    int matrixIdx = 0;
+    for (List<FloatMatrix> aWeightMatrixList: this.weightMatrixLists) {
+      for (int weightMatrixIdx = 0; weightMatrixIdx < aWeightMatrixList.size(); weightMatrixIdx++) {
+        aWeightMatrixList.set(weightMatrixIdx, matrices[matrixIdx++]);
+      }
+    }
+  }
+
+  /**
+   * Get the previous matrices updates in form of array.
+   * 
+   * @return The matrices in form of matrix array.
+   */
+  public FloatMatrix[] getPrevMatricesUpdates() {
+    FloatMatrix[] matrices = new FloatMatrix[this.getSizeOfWeightmatrix()];
+    int matrixIdx = 0;
+    for (List<FloatMatrix> aWeightMatrixList: this.prevWeightUpdatesLists) {
+      for (FloatMatrix aWeightMatrix : aWeightMatrixList) {
+        matrices[matrixIdx++] = aWeightMatrix;
+      }
+    }
+    return matrices;
+  }
+
+  public void setWeightMatrix(int index, FloatMatrix matrix) {
+    Preconditions.checkArgument(
+        0 <= index && index < this.weightMatrixList.size(), String.format(
+            "index [%d] should be in range[%d, %d].", index, 0,
+            this.weightMatrixList.size()));
+    this.weightMatrixList.set(index, matrix);
+  }
+
+  @Override
+  public void readFields(DataInput input) throws IOException {
+    super.readFields(input);
+
+    this.finalLayerIdx = input.readInt();
+    this.dropRate = input.readFloat();
+
+    // read neuron classes
+    int neuronClasses = input.readInt();
+    this.neuronClassList = Lists.newArrayList();
+    for (int i = 0; i < neuronClasses; ++i) {
+      try {
+        Class<? extends Neuron<?>> clazz = (Class<? extends Neuron<?>>) Class
+            .forName(input.readUTF());
+        neuronClassList.add(clazz);
+      } catch (ClassNotFoundException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
+    }
+
+    // read squash functions
+    int squashingFunctionSize = input.readInt();
+    this.squashingFunctionList = Lists.newArrayList();
+    for (int i = 0; i < squashingFunctionSize; ++i) {
+      this.squashingFunctionList.add(FunctionFactory
+          .createFloatFunction(WritableUtils.readString(input)));
+    }
+
+    this.recurrentStepSize = input.readInt();
+    this.numOutCells = input.readInt();
+    int recurrentLayerListSize = input.readInt();
+    this.recurrentLayerList = Lists.newArrayList();
+    for (int i = 0; i < recurrentLayerListSize; i++) {
+      this.recurrentLayerList.add(input.readBoolean());
+    }
+
+    // read weights and construct matrices of previous updates
+    int numOfMatrices = input.readInt();
+    this.weightMatrixLists = Lists.newArrayListWithExpectedSize(this.recurrentStepSize);
+    this.prevWeightUpdatesLists = Lists.newArrayList();
+
+    for (int step = 0; step < this.recurrentStepSize; step++) {
+      this.weightMatrixList = Lists.newArrayList();
+      this.prevWeightUpdatesList = Lists.newArrayList();
+
+      for (int j = 0; j < this.layerSizeList.size() - 2; j++) {
+        FloatMatrix matrix = FloatMatrixWritable.read(input);
+        this.weightMatrixList.add(matrix);
+        this.prevWeightUpdatesList.add(new DenseFloatMatrix(matrix.getRowCount(),
+            matrix.getColumnCount()));
+      }
+      // if the cell has output layer, read from input
+      if (step >= this.recurrentStepSize - this.numOutCells) {
+        FloatMatrix matrix = FloatMatrixWritable.read(input);
+        this.weightMatrixList.add(matrix);
+        this.prevWeightUpdatesList.add(new DenseFloatMatrix(matrix.getRowCount(),
+            matrix.getColumnCount()));
+      }
+      this.weightMatrixLists.add(this.weightMatrixList);
+      this.prevWeightUpdatesLists.add(this.prevWeightUpdatesList);
+    }
+  }
+
+//  }
+  protected int getSizeOfWeightmatrix() {
+    return this.recurrentStepSize * (this.layerSizeList.size() - 2) + this.numOutCells;
+  }
+
+  @Override
+  public void write(DataOutput output) throws IOException {
+    super.write(output);
+    output.writeInt(finalLayerIdx);
+    output.writeFloat(dropRate);
+
+    // write neuron classes
+    output.writeInt(this.neuronClassList.size());
+    for (Class<? extends Neuron<?>> clazz : this.neuronClassList) {
+      output.writeUTF(clazz.getName());
+    }
+
+    // write squashing functions
+    output.writeInt(this.squashingFunctionList.size());
+    for (FloatFunction aSquashingFunctionList : this.squashingFunctionList) {
+      WritableUtils.writeString(output,
+          aSquashingFunctionList.getFunctionName());
+    }
+
+    // write recurrent step size
+    output.writeInt(this.recurrentStepSize);
+
+    // write recurrent step size
+    output.writeInt(this.numOutCells);
+
+    // write recurrent layer list
+    output.writeInt(this.recurrentLayerList.size());
+    for (Boolean isReccurentLayer: recurrentLayerList) {
+      output.writeBoolean(isReccurentLayer);
+    }
+
+    // write weight matrices
+    output.writeInt(this.getSizeOfWeightmatrix());
+    for (List<FloatMatrix> aWeightMatrixLists : this.weightMatrixLists) {
+      for (FloatMatrix aWeightMatrixList : aWeightMatrixLists) {
+        FloatMatrixWritable.write(aWeightMatrixList, output);
+      }
+    }
+
+    // DO NOT WRITE WEIGHT UPDATE
+  }
+
+  @Override
+  public FloatMatrix getWeightsByLayer(int layerIdx) {
+    return this.weightMatrixList.get(layerIdx);
+  }
+
+  public FloatMatrix getWeightsByLayer(int stepIdx, int layerIdx) {
+    return this.weightMatrixLists.get(stepIdx).get(layerIdx);
+  }
+
+  /**
+   * Get the output of the model according to given feature instance.
+   */
+  @Override
+  public FloatVector getOutput(FloatVector instance) {
+    Preconditions.checkArgument((this.layerSizeList.get(0) - 1) * this.recurrentStepSize
+        == instance.getDimension(), String.format(
+            "The dimension of input instance should be %d.",
+            this.layerSizeList.get(0) - 1));
+    // transform the features to another space
+    FloatVector transformedInstance = this.featureTransformer
+        .transform(instance);
+    // add bias feature
+    FloatVector instanceWithBias = new DenseFloatVector(
+        transformedInstance.getDimension() + 1);
+    instanceWithBias.set(0, 0.99999f); // set bias to be a little bit less than
+                                       // 1.0
+    for (int i = 1; i < instanceWithBias.getDimension(); ++i) {
+      instanceWithBias.set(i, transformedInstance.get(i - 1));
+    }
+    // return the output of the last layer
+    return getOutputInternal(instanceWithBias);
+  }
+
+  public void setDropRateOfInputLayer(float dropRate) {
+    this.dropRate = dropRate;
+  }
+
+  /**
+   * Calculate output internally, the intermediate output of each layer will be
+   * stored.
+   * 
+   * @param instanceWithBias The instance contains the features.
+   * @return Cached output of each layer.
+   */
+  public FloatVector getOutputInternal(FloatVector instanceWithBias) {
+    // sets the output of input layer
+    Neuron<?>[] inputLayer;
+    for (int stepIdx = 0; stepIdx < this.weightMatrixLists.size(); stepIdx++) {
+      inputLayer = neuronLists.get(stepIdx).get(0);
+      for (int inputNeuronIdx = 0; inputNeuronIdx < this.layerSizeList.get(0); inputNeuronIdx++) {
+        float m2 = MathUtils.getBinomial(1, dropRate);
+        if(m2 == 0)
+          inputLayer[inputNeuronIdx].setDrop(true);
+        else
+          inputLayer[inputNeuronIdx].setDrop(false);
+        inputLayer[inputNeuronIdx].setOutput(
+            instanceWithBias.get(stepIdx * this.layerSizeList.get(0) + inputNeuronIdx) * m2);
+      }
+      // loop forward as much as recurrent step size
+      this.weightMatrixList = this.weightMatrixLists.get(stepIdx);
+      for (int layerIdx = 0; layerIdx < weightMatrixList.size(); ++layerIdx) {
+        forward(stepIdx, layerIdx);
+      }
+    }
+
+    // output for each recurrent step
+    int singleOutputLength = 
+        neuronLists.get(this.recurrentStepSize-1).get(this.finalLayerIdx).length;
+    FloatVector output = new DenseFloatVector(singleOutputLength * this.numOutCells);
+    int outputNeuronIdx = 0;
+    for (int step = this.recurrentStepSize - this.numOutCells; 
+        step < this.recurrentStepSize; step++) {
+      neurons = neuronLists.get(step);
+      for (int neuronIdx = 0; neuronIdx < singleOutputLength; neuronIdx++) {
+        output.set(outputNeuronIdx, neurons.get(this.finalLayerIdx)[neuronIdx].getOutput());
+        outputNeuronIdx++;
+      }
+    }
+
+    return output;
+  }
+
+  /**
+   * @param neuronClass
+   * @return a new neuron instance
+   */
+  @SuppressWarnings({ "rawtypes" })
+  public static Neuron newNeuronInstance(Class<? extends Neuron> neuronClass) {
+    return (Neuron) ReflectionUtils.newInstance(neuronClass);
+  }
+
+  public class InputMessageIterable implements
+      Iterable<Synapse<FloatWritable, FloatWritable>> {
+    private int currNeuronID;
+    private int prevNeuronID;
+    private int end;
+    private FloatMatrix weightMat;
+    private Neuron<?>[] layer;
+
+    public InputMessageIterable(int fromLayer, int row) {
+      this.currNeuronID = row;
+      this.prevNeuronID = -1;
+      this.end = weightMatrixList.get(fromLayer).getColumnCount() - 1;
+      this.weightMat = weightMatrixList.get(fromLayer);
+      this.layer = neurons.get(fromLayer);
+    }
+
+    @Override
+    public Iterator<Synapse<FloatWritable, FloatWritable>> iterator() {
+      return new MessageIterator();
+    }
+
+    private class MessageIterator implements
+        Iterator<Synapse<FloatWritable, FloatWritable>> {
+
+      @Override
+      public boolean hasNext() {
+        if (prevNeuronID < end) {
+          return true;
+        } else {
+          return false;
+        }
+      }
+
+      private FloatWritable i = new FloatWritable();
+      private FloatWritable w = new FloatWritable();
+      private Synapse<FloatWritable, FloatWritable> msg = new Synapse<FloatWritable, FloatWritable>();
+      
+      @Override
+      public Synapse<FloatWritable, FloatWritable> next() {
+        prevNeuronID++;
+        i.set(layer[prevNeuronID].getOutput());
+        w.set(weightMat.get(currNeuronID, prevNeuronID));
+        msg.set(prevNeuronID, i, w);
+        return new Synapse<FloatWritable, FloatWritable>(prevNeuronID, i, w);
+      }
+    
+      @Override
+      public void remove() {
+      }
+    }
+  }
+
+  /**
+   * Forward the calculation for one layer.
+   * 
+   * @param fromLayerIdx The index of the previous layer.
+   */
+  protected void forward(int stepIdx, int fromLayerIdx) {
+    neurons = this.neuronLists.get(stepIdx);
+    int curLayerIdx = fromLayerIdx + 1;
+    // weight matrix for current layer
+    FloatMatrix weightMatrix = this.weightMatrixList.get(fromLayerIdx);
+    FloatFunction squashingFunction = getSquashingFunction(fromLayerIdx);
+    FloatVector vec = new DenseFloatVector(weightMatrix.getRowCount());
+
+    for (int row = 0; row < weightMatrix.getRowCount(); row++) {
+      Neuron<?> n;
+      if (curLayerIdx == finalLayerIdx)
+        n = neurons.get(curLayerIdx)[row];
+      else
+        n = neurons.get(curLayerIdx)[row + 1];
+
+      try {
+        Iterable msgs = new InputMessageIterable(fromLayerIdx, row);
+        ((RecurrentDropoutNeuron) n).setRecurrentDelta(0);
+        n.setIterationNumber(iterations);
+        n.forward(msgs);
+      } catch (IOException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
+      vec.set(row, n.getOutput());
+    }
+
+    if (squashingFunction.getFunctionName().equalsIgnoreCase(
+        SoftMax.class.getSimpleName())) {
+      IntermediateOutput interlayer = (IntermediateOutput) ReflectionUtils
+          .newInstance(SoftMax.SoftMaxOutputComputer.class);
+      try {
+        vec = interlayer.interlayer(vec);
+
+        for (int i = 0; i < vec.getDimension(); i++) {
+          neurons.get(curLayerIdx)[i].setOutput(vec.get(i));
+        }
+      } catch (IOException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
+    }
+
+    // add bias
+    if (curLayerIdx != finalLayerIdx)
+      neurons.get(curLayerIdx)[0].setOutput(1);
+
+    // copy output to next recurrent layer
+    if (this.recurrentLayerList.get(curLayerIdx) && stepIdx < this.recurrentStepSize - 1) {
+      for (int i = 0; i < vec.getDimension(); i++) {
+        this.neuronLists.get(stepIdx+1).get(
+            fromLayerIdx)[this.layerSizeList.get(fromLayerIdx)+i].setOutput(vec.get(i));
+      }
+    }
+  }
+
+  /**
+   * Train the model online.
+   * 
+   * @param trainingInstance
+   */
+  public void trainOnline(FloatVector trainingInstance) {
+    FloatMatrix[] updateMatrices = this.trainByInstance(trainingInstance);
+    this.updateWeightMatrices(updateMatrices);
+  }
+
+  @Override
+  public FloatMatrix[] trainByInstance(FloatVector trainingInstance) {
+    int inputDimension =  (this.layerSizeList.get(0) - 1) * this.recurrentStepSize;
+    FloatVector transformedVector = this.featureTransformer.transform(
+        trainingInstance.sliceUnsafe(inputDimension));
+    int outputDimension;
+    FloatVector inputInstance = null;
+    FloatVector labels = null;
+    if (this.learningStyle == LearningStyle.SUPERVISED) {
+      outputDimension = this.layerSizeList.get(this.layerSizeList.size() - 1);
+        // validate training instance
+        Preconditions.checkArgument(
+            (inputDimension + outputDimension == trainingInstance.getDimension()
+            || inputDimension + outputDimension * recurrentStepSize == trainingInstance.getDimension()),
+            String
+                .format(
+                    "The dimension of training instance is %d, but requires %d.",
+                    trainingInstance.getDimension(), inputDimension + outputDimension));
+
+      inputInstance = new DenseFloatVector(this.layerSizeList.get(0) * this.recurrentStepSize);
+      // get the features from the transformed vector
+      int vecIdx = 0;
+      for (int i = 0; i < inputInstance.getLength(); ++i) {
+        if (i % this.layerSizeList.get(0) == 0) {
+          inputInstance.set(i, 1); // add bias
+        } else {
+          inputInstance.set(i, transformedVector.get(vecIdx));
+          vecIdx++;
+        }
+      }
+      // get the labels from the original training instance
+      labels = trainingInstance.sliceUnsafe(transformedVector.getDimension(),
+          trainingInstance.getDimension() - 1);
+    } else if (this.learningStyle == LearningStyle.UNSUPERVISED) {
+      // labels are identical to input features
+      outputDimension = inputDimension;
+      // validate training instance
+      Preconditions.checkArgument(inputDimension == trainingInstance
+          .getDimension(), String.format(
+          "The dimension of training instance is %d, but requires %d.",
+          trainingInstance.getDimension(), inputDimension));
+
+      inputInstance = new DenseFloatVector(this.layerSizeList.get(0) * this.recurrentStepSize);
+      // get the features from the transformed vector
+      int vecIdx = 0;
+      for (int i = 0; i < inputInstance.getLength(); ++i) {
+        if (i % this.layerSizeList.get(0) == 0) {
+          inputInstance.set(i, 1); // add bias
+        } else {
+          inputInstance.set(i, transformedVector.get(vecIdx));
+          vecIdx++;
+        }
+      }
+      // get the labels by copying the transformed vector
+      labels = transformedVector.deepCopy();
+    }
+    FloatVector output = this.getOutputInternal(inputInstance);
+    // get the training error
+    calculateTrainingError(labels, output);
+    if (this.trainingMethod.equals(TrainingMethod.GRADIENT_DESCENT)) {
+      FloatMatrix[] updates = this.trainByInstanceGradientDescent(labels);
+      return updates;
+    } else {
+      throw new IllegalArgumentException(
+          String.format("Training method is not supported."));
+    }
+  }
+
+  /**
+   * Train by gradient descent. Get the updated weights using one training
+   * instance.
+   * 
+   * @param trainingInstance
+   * @return The weight update matrices.
+   */
+  private FloatMatrix[] trainByInstanceGradientDescent(FloatVector labels) {
+
+    // initialize weight update matrices
+    DenseFloatMatrix[] weightUpdateMatrices = new DenseFloatMatrix[this.getSizeOfWeightmatrix()];
+    int matrixIdx = 0;
+    for (List<FloatMatrix> aWeightMatrixList: this.weightMatrixLists) {
+      for (FloatMatrix aWeightMatrix : aWeightMatrixList) {
+        weightUpdateMatrices[matrixIdx++] = 
+            new DenseFloatMatrix(aWeightMatrix.getRowCount(), aWeightMatrix.getColumnCount());
+      }
+    }
+    FloatVector deltaVec = new DenseFloatVector(
+        this.layerSizeList.get(layerSizeList.size() - 1) * this.numOutCells);
+
+    FloatFunction squashingFunction = this.squashingFunctionList
+        .get(this.squashingFunctionList.size() - 1);
+
+    int labelIdx = 0;
+    // start from last recurrent step to first recurrent step
+    for (int step=this.recurrentStepSize-this.numOutCells; step < this.recurrentStepSize; step++) {
+
+      FloatMatrix lastWeightMatrix = this.weightMatrixLists.get(step)
+          .get(this.weightMatrixLists.get(step).size() - 1);
+      int neuronIdx = 0;
+      for (Neuron<?> aNeurons : this.neuronLists.get(step).get(this.finalLayerIdx)) {
+        float finalOut = aNeurons.getOutput();
+        float costFuncDerivative = this.costFunction.applyDerivative(
+            labels.get(labelIdx), finalOut);
+        // add regularization
+        costFuncDerivative += this.regularizationWeight
+            * lastWeightMatrix.getRowVector(neuronIdx).sum();
+  
+        if (!squashingFunction.getFunctionName().equalsIgnoreCase(
+            SoftMax.class.getSimpleName())) {
+          costFuncDerivative *= squashingFunction.applyDerivative(finalOut);
+        }
+        aNeurons.backpropagate(costFuncDerivative);
+        deltaVec.set(labelIdx, costFuncDerivative);
+        neuronIdx++;
+        labelIdx++;
+      }
+    }
+
+    // start from last recurrent step to first recurrent step
+    boolean skipLastLayer = false;
+    int weightMatrixIdx = weightUpdateMatrices.length - 1;
+    for (int step = this.recurrentStepSize - 1 ; step >= 0; --step) {
+      this.weightMatrixList = this.weightMatrixLists.get(step);
+      this.prevWeightUpdatesList = this.prevWeightUpdatesLists.get(step);
+      this.neurons = this.neuronLists.get(step);
+      if (step < this.recurrentStepSize - this.numOutCells)
+        skipLastLayer = true;
+      // start from previous layer of output layer
+      for (int layer = this.layerSizeList.size() - 2; layer >= 0; --layer) {
+        if (skipLastLayer) {
+          skipLastLayer = false; continue;
+        }
+        backpropagate(step, layer, weightUpdateMatrices[weightMatrixIdx--]);
+      }
+    }
+    // TODO eliminate non-output cells from weightUpdateLists
+    this.setPrevWeightMatrices(weightUpdateMatrices);
+    return weightUpdateMatrices;
+  }
+
+  public class ErrorMessageIterable implements
+      Iterable<Synapse<FloatWritable, FloatWritable>> {
+    private int row;
+    private int neuronID;
+    private int end;
+    private FloatMatrix weightMat;
+    private FloatMatrix prevWeightMat;
+
+    private float[] nextLayerDelta;
+    
+    public ErrorMessageIterable(int recurrentStepIdx, int curLayerIdx, int row) {
+      this.row = row;
+      this.neuronID = -1;
+      this.weightMat = weightMatrixLists.get(recurrentStepIdx).get(curLayerIdx);
+      this.end = weightMat.getRowCount() - 1;
+      this.prevWeightMat = prevWeightUpdatesLists.get(recurrentStepIdx).get(curLayerIdx);
+      
+      Neuron<?>[] nextLayer = neuronLists.get(recurrentStepIdx).get(curLayerIdx + 1);
+      nextLayerDelta = new float[weightMat.getRowCount()];
+      
+      for(int i = 0; i <= end; ++i) {
+        if (curLayerIdx + 1 == finalLayerIdx) {
+          nextLayerDelta[i] = nextLayer[i].getDelta();
+        } else {
+          nextLayerDelta[i] = nextLayer[i + 1].getDelta();
+        }
+      }
+    }
+
+    @Override
+    public Iterator<Synapse<FloatWritable, FloatWritable>> iterator() {
+      return new MessageIterator();
+    }
+
+    private class MessageIterator implements
+        Iterator<Synapse<FloatWritable, FloatWritable>> {
+
+      @Override
+      public boolean hasNext() {
+        if (neuronID < end) {
+          return true;
+        } else {
+          return false;
+        }
+      }
+
+      private FloatWritable d = new FloatWritable();
+      private FloatWritable w = new FloatWritable();
+      private FloatWritable p = new FloatWritable();
+      private Synapse<FloatWritable, FloatWritable> msg = new Synapse<FloatWritable, FloatWritable>();
+      
+      @Override
+      public Synapse<FloatWritable, FloatWritable> next() {
+        neuronID++;
+        
+        d.set(nextLayerDelta[neuronID]);
+        w.set(weightMat.get(neuronID, row));
+        p.set(prevWeightMat.get(neuronID, row));
+        msg.set(neuronID, d, w, p);
+        return msg;
+      }
+
+      @Override
+      public void remove() {
+      }
+
+    }
+  }
+
+  /**
+   * Back-propagate the errors to from next layer to current layer. The weight
+   * updated information will be stored in the weightUpdateMatrices, and the
+   * delta of the prevLayer would be returned.
+   * 
+   * @param layer Index of current layer.
+   */
+  private void backpropagate(int recurrentStepIdx, int curLayerIdx,
+      DenseFloatMatrix weightUpdateMatrix) {
+
+    // get layer related information
+    int x = this.weightMatrixList.get(curLayerIdx).getColumnCount();
+    int y = this.weightMatrixList.get(curLayerIdx).getRowCount();
+
+    FloatVector deltaVector = new DenseFloatVector(x);
+    Neuron<?>[] ns = this.neuronLists.get(recurrentStepIdx).get(curLayerIdx);
+
+    for (int row = 0; row < x; ++row) {
+      Neuron<?> n = ns[row];
+      n.setWeightVector(y);
+
+      try {
+        Iterable msgs = new ErrorMessageIterable(recurrentStepIdx, curLayerIdx, row);
+        n.backward(msgs);
+        if (row >= layerSizeList.get(curLayerIdx) && recurrentStepIdx > 0
+            && recurrentLayerList.get(curLayerIdx+1)) {
+          Neuron<?> recurrentNeuron = neuronLists.get(recurrentStepIdx-1).get(curLayerIdx+1)
+              [row-layerSizeList.get(curLayerIdx)+1];
+          recurrentNeuron.backpropagate(n.getDelta());
+        }
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+      // update weights
+      weightUpdateMatrix.setColumn(row, n.getWeights());
+      deltaVector.set(row, n.getDelta());
+    }
+  }
+
+  @Override
+  protected BSPJob trainInternal(HamaConfiguration conf) throws IOException,
+      InterruptedException, ClassNotFoundException {
+    this.conf = conf;
+    this.fs = FileSystem.get(conf);
+
+    String modelPath = conf.get("model.path");
+    if (modelPath != null) {
+      this.modelPath = modelPath;
+    }
+    // modelPath must be set before training
+    if (this.modelPath == null) {
+      throw new IllegalArgumentException(
+          "Please specify the modelPath for model, "
+              + "either through setModelPath() or add 'modelPath' to the training parameters.");
+    }
+    this.setRecurrentStepSize(conf.getInt("training.recurrent.step.size", 1));
+    this.initializeWeightMatrixLists();
+    this.writeModelToFile();
+
+    // create job
+    BSPJob job = new BSPJob(conf, RecurrentLayeredNeuralNetworkTrainer.class);
+    job.setJobName("Neural Network training");
+    job.setJarByClass(RecurrentLayeredNeuralNetworkTrainer.class);
+    job.setBspClass(RecurrentLayeredNeuralNetworkTrainer.class);
+
+    job.getConfiguration().setInt(Constants.ADDITIONAL_BSP_TASKS, 1);
+
+    job.setBoolean("training.mode", true);
+    job.setInputPath(new Path(conf.get("training.input.path")));
+    job.setInputFormat(org.apache.hama.bsp.SequenceFileInputFormat.class);
+    job.setInputKeyClass(LongWritable.class);
+    job.setInputValueClass(VectorWritable.class);
+    job.setOutputKeyClass(NullWritable.class);
+    job.setOutputValueClass(NullWritable.class);
+    job.setOutputFormat(org.apache.hama.bsp.NullOutputFormat.class);
+
+    return job;
+  }
+
+  @Override
+  protected void calculateTrainingError(FloatVector labels, FloatVector output) {
+    FloatVector errors = labels.deepCopy().applyToElements(output,
+        this.costFunction);
+    this.trainingError = errors.sum();
+  }
+
+  /**
+   * Get the squashing function of a specified layer.
+   * 
+   * @param idx
+   * @return a new vector with the result of the operation.
+   */
+  public FloatFunction getSquashingFunction(int idx) {
+    return this.squashingFunctionList.get(idx);
+  }
+
+  public void setIterationNumber(long iterations) {
+    this.iterations = iterations;
+  }
+
+  public void setRecurrentStepSize(int recurrentStepSize) {
+    this.recurrentStepSize = recurrentStepSize;
+  }
+
+}
diff --git a/src/main/java/org/apache/horn/core/RecurrentLayeredNeuralNetworkTrainer.java b/src/main/java/org/apache/horn/core/RecurrentLayeredNeuralNetworkTrainer.java
new file mode 100644
index 0000000..adf8b8c
--- /dev/null
+++ b/src/main/java/org/apache/horn/core/RecurrentLayeredNeuralNetworkTrainer.java
@@ -0,0 +1,286 @@
+/**
+ * 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.
+ */
+package org.apache.horn.core;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.io.LongWritable;
+import org.apache.hadoop.io.NullWritable;
+import org.apache.hama.HamaConfiguration;
+import org.apache.hama.bsp.BSP;
+import org.apache.hama.bsp.BSPPeer;
+import org.apache.hama.bsp.sync.SyncException;
+import org.apache.hama.commons.io.FloatVectorWritable;
+import org.apache.hama.commons.math.DenseFloatMatrix;
+import org.apache.hama.commons.math.FloatMatrix;
+import org.apache.hama.commons.math.FloatVector;
+
+/**
+ * The trainer that train the {@link RecurrentLayeredNeuralNetwork} based on BSP
+ * framework.
+ * 
+ */
+public final class RecurrentLayeredNeuralNetworkTrainer
+    extends
+    BSP<LongWritable, FloatVectorWritable, NullWritable, NullWritable, ParameterMessage> {
+
+  private static final Log LOG = LogFactory
+      .getLog(RecurrentLayeredNeuralNetworkTrainer.class);
+
+  private RecurrentLayeredNeuralNetwork inMemoryModel;
+  private HamaConfiguration conf;
+  /* Default batch size */
+  private int batchSize;
+
+  /* check the interval between intervals */
+  private double prevAvgTrainingError;
+  private double curAvgTrainingError;
+  private long convergenceCheckInterval;
+  private long iterations;
+  private long maxIterations;
+  private boolean isConverge;
+
+  private String modelPath;
+
+  @Override
+  /**
+   * If the model path is specified, load the existing from storage location.
+   */
+  public void setup(
+      BSPPeer<LongWritable, FloatVectorWritable, NullWritable, NullWritable, ParameterMessage> peer) {
+    if (peer.getPeerIndex() == 0) {
+      LOG.info("Begin to train");
+    }
+    this.isConverge = false;
+    this.conf = peer.getConfiguration();
+    this.iterations = 0;
+    this.modelPath = conf.get("model.path");
+    this.maxIterations = conf.getLong("training.max.iterations", Long.MAX_VALUE);
+    this.convergenceCheckInterval = conf.getLong("convergence.check.interval",
+        100);
+    this.inMemoryModel = new RecurrentLayeredNeuralNetwork(conf, modelPath, true);
+    this.prevAvgTrainingError = Integer.MAX_VALUE;
+    this.batchSize = conf.getInt("training.batch.size", 5);
+  }
+
+  @Override
+  /**
+   * Write the trained model back to stored location.
+   */
+  public void cleanup(
+      BSPPeer<LongWritable, FloatVectorWritable, NullWritable, NullWritable, ParameterMessage> peer) {
+    // write model to modelPath
+    if (peer.getPeerIndex() == peer.getNumPeers() - 1) {
+      try {
+        LOG.info(String.format("End of training, number of iterations: %d.",
+            this.iterations));
+        LOG.info(String.format("Write model back to %s",
+            inMemoryModel.getModelPath()));
+        this.inMemoryModel.writeModelToFile();
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  private List<FloatVector> trainingSet = new ArrayList<FloatVector>();
+  private Random r = new Random();
+
+  @Override
+  public void bsp(
+      BSPPeer<LongWritable, FloatVectorWritable, NullWritable, NullWritable, ParameterMessage> peer)
+      throws IOException, SyncException, InterruptedException {
+    // load local data into memory
+    LongWritable key = new LongWritable();
+    FloatVectorWritable value = new FloatVectorWritable();
+    while (peer.readNext(key, value)) {
+      FloatVector v = value.getVector();
+      trainingSet.add(v);
+    }
+
+    if (peer.getPeerIndex() != peer.getNumPeers() - 1) {
+      LOG.debug(peer.getPeerName() + ": " + trainingSet.size() + " training instances loaded.");
+    }
+    
+    while (this.iterations++ < maxIterations) {
+      this.inMemoryModel.setIterationNumber(iterations);
+      
+      // each groom calculate the matrices updates according to local data
+      if (peer.getPeerIndex() != peer.getNumPeers() - 1) {
+        calculateUpdates(peer);
+      } else {
+        // doing summation received updates
+        if (peer.getSuperstepCount() > 0) {
+          // and broadcasts previous updated weights
+          mergeUpdates(peer);
+        }
+      }
+
+      peer.sync();
+
+      if (maxIterations == Long.MAX_VALUE && isConverge) {
+        if (peer.getPeerIndex() == peer.getNumPeers() - 1)
+          peer.sync();
+        break;
+      }
+    }
+
+    peer.sync();
+    if (peer.getPeerIndex() == peer.getNumPeers() - 1)
+      mergeUpdates(peer); // merge last updates
+  }
+
+  private FloatVector getRandomInstance() {
+    return trainingSet.get(r.nextInt(trainingSet.size()));
+  }
+
+  /**
+   * Calculate the matrices updates according to local partition of data.
+   * 
+   * @param peer
+   * @throws IOException
+   */
+  private void calculateUpdates(
+      BSPPeer<LongWritable, FloatVectorWritable, NullWritable, NullWritable, ParameterMessage> peer)
+      throws IOException {
+    // receive update information from master
+    if (peer.getNumCurrentMessages() != 0) {
+      ParameterMessage inMessage = peer.getCurrentMessage();
+      FloatMatrix[] newWeights = inMessage.getCurMatrices();
+      FloatMatrix[] preWeightUpdates = inMessage.getPrevMatrices();
+      this.inMemoryModel.setWeightMatrices(newWeights);
+      this.inMemoryModel.setPrevWeightMatrices(preWeightUpdates);
+      this.isConverge = inMessage.isConverge();
+      // check converge
+      if (isConverge) {
+        return;
+      }
+    }
+
+    FloatMatrix[] weightUpdates = new FloatMatrix[this.inMemoryModel.getSizeOfWeightmatrix()];
+    int matrixIdx = 0;
+    for (List<FloatMatrix> aWeightMatrixList: this.inMemoryModel.weightMatrixLists) {
+      for (FloatMatrix aWeightMatrix : aWeightMatrixList) {
+        weightUpdates[matrixIdx++] = new DenseFloatMatrix(
+            aWeightMatrix.getRowCount(), aWeightMatrix.getColumnCount());
+      }
+    }
+
+    // continue to train
+    float avgTrainingError = 0.0f;
+    for (int recordsRead = 0; recordsRead < batchSize; ++recordsRead) {
+      FloatVector trainingInstance = getRandomInstance();
+      RecurrentLayeredNeuralNetwork.matricesAdd(
+          weightUpdates, this.inMemoryModel.trainByInstance(trainingInstance));
+      avgTrainingError += this.inMemoryModel.trainingError;
+    }
+    avgTrainingError /= batchSize;
+
+    // calculate the average of updates
+    for (int i = 0; i < weightUpdates.length; ++i) {
+      weightUpdates[i] = weightUpdates[i].divide(batchSize);
+    }
+
+    FloatMatrix[] prevWeightUpdates = this.inMemoryModel
+        .getPrevMatricesUpdates();
+    ParameterMessage outMessage = new ParameterMessage(avgTrainingError, false,
+        weightUpdates, prevWeightUpdates);
+
+    peer.send(peer.getPeerName(peer.getNumPeers() - 1), outMessage);
+  }
+
+  /**
+   * Merge the updates according to the updates of the grooms.
+   * 
+   * @param peer
+   * @throws IOException
+   */
+  private void mergeUpdates(
+      BSPPeer<LongWritable, FloatVectorWritable, NullWritable, NullWritable, ParameterMessage> peer)
+      throws IOException {
+    int numMessages = peer.getNumCurrentMessages();
+    boolean converge = false;
+    if (numMessages == 0) { // converges
+      converge = true;
+      return;
+    }
+
+    double avgTrainingError = 0;
+    FloatMatrix[] matricesUpdates = null;
+    FloatMatrix[] prevMatricesUpdates = null;
+
+    while (peer.getNumCurrentMessages() > 0) {
+      ParameterMessage message = peer.getCurrentMessage();
+      if (matricesUpdates == null) {
+        matricesUpdates = message.getCurMatrices();
+        prevMatricesUpdates = message.getPrevMatrices();
+      } else {
+        RecurrentLayeredNeuralNetwork.matricesAdd(matricesUpdates,
+            message.getCurMatrices());
+        RecurrentLayeredNeuralNetwork.matricesAdd(prevMatricesUpdates,
+            message.getPrevMatrices());
+      }
+
+      avgTrainingError += message.getTrainingError();
+    }
+
+    if (numMessages > 1) {
+      avgTrainingError /= numMessages;
+      for (int i = 0; i < matricesUpdates.length; ++i) {
+        matricesUpdates[i] = matricesUpdates[i].divide(numMessages);
+        prevMatricesUpdates[i] = prevMatricesUpdates[i].divide(numMessages);
+      }
+    }
+
+    this.inMemoryModel.updateWeightMatrices(matricesUpdates);
+    this.inMemoryModel.setPrevWeightMatrices(prevMatricesUpdates);
+
+    // check convergence
+    if (peer.getSuperstepCount() > 0
+        && iterations % convergenceCheckInterval == 0) {
+      if (prevAvgTrainingError < curAvgTrainingError) {
+        // error cannot decrease any more
+        converge = true;
+      }
+      // update
+      prevAvgTrainingError = curAvgTrainingError;
+      LOG.info("Training error: " + curAvgTrainingError + " at " + (iterations)
+          + " iteration.");
+      curAvgTrainingError = 0;
+    }
+    curAvgTrainingError += avgTrainingError / convergenceCheckInterval;
+    this.isConverge = converge;
+
+    if (iterations < maxIterations) {
+      // broadcast updated weight matrices
+      for (String peerName : peer.getAllPeerNames()) {
+        ParameterMessage msg = new ParameterMessage(0, converge,
+            this.inMemoryModel.getWeightMatrices(),
+            this.inMemoryModel.getPrevMatricesUpdates());
+        if (!peer.getPeerName().equals(peerName))
+          peer.send(peerName, msg);
+      }
+    }
+  }
+
+}
diff --git a/src/main/java/org/apache/horn/examples/ExampleDriver.java b/src/main/java/org/apache/horn/examples/ExampleDriver.java
index d9f63a3..b4c1964 100644
--- a/src/main/java/org/apache/horn/examples/ExampleDriver.java
+++ b/src/main/java/org/apache/horn/examples/ExampleDriver.java
@@ -18,6 +18,7 @@
 package org.apache.horn.examples;
 
 import org.apache.hadoop.util.ProgramDriver;
+import org.apache.horn.utils.ExclusiveOrConverter;
 import org.apache.horn.utils.MNISTConverter;
 import org.apache.horn.utils.MNISTEvaluator;
 
@@ -32,12 +33,26 @@
           MNISTConverter.class,
           "A utility program that converts MNIST training and label datasets "
           + "into HDFS sequence file.");
-      pgd.addClass("MNISTEvaluator", MNISTEvaluator.class,
+      pgd.addClass("MNISTEvaluator",
+          MNISTEvaluator.class,
           "A utility program that evaluates trained model for the MNIST dataset");
       pgd.addClass(
           "MultiLayerPerceptron",
           MultiLayerPerceptron.class,
           "An example program that trains a multilayer perceptron model from HDFS sequence file.");
+      pgd.addClass("ExclusiveOrConverter",
+          ExclusiveOrConverter.class,
+          "A utility program that converts ExclusiveOR training and label datasets ");
+      pgd.addClass(
+          "ExclusiveOrRecurrentMultiLayerPerceptron",
+          ExclusiveOrRecurrentMultiLayerPerceptron.class,
+          "An example program that trains a recurrent multilayer perceptron model with exclusive or"
+          + " from HDFS sequence file.");
+      pgd.addClass(
+          "MnistRecurrentMultiLayerPerceptron",
+          MnistRecurrentMultiLayerPerceptron.class,
+          "An example program that trains a recurrent multilayer perceptron model with MNIST"
+          + " from HDFS sequence file.");
       exitCode = pgd.run(args);
     } catch (Throwable e) {
       e.printStackTrace();
diff --git a/src/main/java/org/apache/horn/examples/ExclusiveOrRecurrentMultiLayerPerceptron.java b/src/main/java/org/apache/horn/examples/ExclusiveOrRecurrentMultiLayerPerceptron.java
new file mode 100644
index 0000000..6e88217
--- /dev/null
+++ b/src/main/java/org/apache/horn/examples/ExclusiveOrRecurrentMultiLayerPerceptron.java
@@ -0,0 +1,131 @@
+/**
+ * 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.
+ */
+package org.apache.horn.examples;
+
+import java.io.IOException;
+
+import org.apache.hadoop.io.FloatWritable;
+import org.apache.hama.HamaConfiguration;
+import org.apache.horn.core.Constants.TrainingMethod;
+import org.apache.horn.core.HornJob;
+import org.apache.horn.core.Neuron;
+import org.apache.horn.core.RecurrentDropoutNeuron;
+import org.apache.horn.core.RecurrentLayeredNeuralNetwork;
+import org.apache.horn.core.Synapse;
+import org.apache.horn.funcs.SoftMax;
+import org.apache.horn.funcs.SquaredError;
+import org.apache.horn.funcs.Tanh;
+
+public class ExclusiveOrRecurrentMultiLayerPerceptron {
+
+  public static class StandardNeuron extends
+      Neuron<Synapse<FloatWritable, FloatWritable>> {
+
+    @Override
+    public void forward(Iterable<Synapse<FloatWritable, FloatWritable>> messages)
+        throws IOException {
+      float sum = 0;
+      for (Synapse<FloatWritable, FloatWritable> m : messages) {
+        sum += m.getInput() * m.getWeight();
+      }
+      this.feedforward(squashingFunction.apply(sum));
+    }
+
+    @Override
+    public void backward(
+        Iterable<Synapse<FloatWritable, FloatWritable>> messages)
+        throws IOException {
+      float delta = 0;
+
+      if (!this.isDropped()) {
+        for (Synapse<FloatWritable, FloatWritable> m : messages) {
+          // Calculates error gradient for each neuron
+          delta += (m.getDelta() * m.getWeight());
+
+          // Weight corrections
+          float weight = -this.getLearningRate() * m.getDelta()
+              * this.getOutput() + this.getMomentumWeight() * m.getPrevWeight();
+          this.push(weight);
+        }
+      }
+
+      this.backpropagate(delta * squashingFunction.applyDerivative(getOutput()));
+    }
+  }
+
+  public static HornJob createJob(HamaConfiguration conf, String modelPath,
+      String inputPath, float learningRate, float momemtumWeight,
+      float regularizationWeight, int features, int hu, int labels,
+      int stepSize, int numOutCells, int miniBatch, int maxIteration)
+          throws IOException, InstantiationException, IllegalAccessException {
+
+    boolean isRecurrent = (stepSize == 1 ? false: true);
+
+    HornJob job = new HornJob(
+        conf, RecurrentLayeredNeuralNetwork.class, ExclusiveOrRecurrentMultiLayerPerceptron.class);
+    job.setTrainingSetPath(inputPath);
+    job.setModelPath(modelPath);
+
+    job.setMaxIteration(maxIteration);
+    job.setLearningRate(learningRate);
+    job.setMomentumWeight(momemtumWeight);
+    job.setRegularizationWeight(regularizationWeight);
+
+    job.setConvergenceCheckInterval(10);
+    job.setBatchSize(miniBatch);
+    job.setRecurrentStepSize(stepSize);
+
+    job.setTrainingMethod(TrainingMethod.GRADIENT_DESCENT);
+
+    job.inputLayer(features, 1.0f); // droprate
+    job.addLayer(hu, Tanh.class, RecurrentDropoutNeuron.class, isRecurrent);
+    job.addLayer(hu, Tanh.class, RecurrentDropoutNeuron.class, isRecurrent);
+    job.outputLayer(labels, SoftMax.class, RecurrentDropoutNeuron.class, numOutCells);
+
+    job.setCostFunction(SquaredError.class);
+
+    return job;
+  }
+
+  public static void main(String[] args) throws IOException,
+      InterruptedException, ClassNotFoundException,
+      NumberFormatException, InstantiationException, IllegalAccessException {
+    if (args.length < 9) {
+      System.out.println("Usage: <MODEL_PATH> <INPUT_PATH> "
+          + "<LEARNING_RATE> <MOMEMTUM_WEIGHT> <REGULARIZATION_WEIGHT> "
+          + "<FEATURE_DIMENSION> <HIDDEN_UNITS> <LABEL_DIMENSION> "
+          + "<STEP_SIZE> <NUM_OUTPUTCELLS> <BATCH_SIZE> <MAX_ITERATION>");
+      System.out.println("E.g. MnistRecurrentMultiLayerPerceptron"
+          + " ./model_semi_rnn semi.seq 0.01 0.9 0.0005 1 2 2 2 1 10 10000");
+      System.exit(-1);
+    }
+
+    HornJob rnn = createJob(new HamaConfiguration(), args[0], args[1],
+        Float.parseFloat(args[2]), Float.parseFloat(args[3]),
+        Float.parseFloat(args[4]), Integer.parseInt(args[5]),
+        Integer.parseInt(args[6]), Integer.parseInt(args[7]),
+        Integer.parseInt(args[8]), Integer.parseInt(args[9]),
+        Integer.parseInt(args[10]), Integer.parseInt(args[11]));
+
+    long startTime = System.currentTimeMillis();
+    if (rnn.waitForCompletion(true)) {
+      System.out.println("Optimization Finished! "
+          + (System.currentTimeMillis() - startTime) / 1000.0 + " seconds");
+    }
+  }
+}
diff --git a/src/main/java/org/apache/horn/examples/MnistRecurrentMultiLayerPerceptron.java b/src/main/java/org/apache/horn/examples/MnistRecurrentMultiLayerPerceptron.java
new file mode 100644
index 0000000..81e17c5
--- /dev/null
+++ b/src/main/java/org/apache/horn/examples/MnistRecurrentMultiLayerPerceptron.java
@@ -0,0 +1,131 @@
+/**
+ * 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.
+ */
+package org.apache.horn.examples;
+
+import java.io.IOException;
+
+import org.apache.hadoop.io.FloatWritable;
+import org.apache.hama.HamaConfiguration;
+import org.apache.horn.core.Constants.TrainingMethod;
+import org.apache.horn.core.HornJob;
+import org.apache.horn.core.Neuron;
+import org.apache.horn.core.RecurrentDropoutNeuron;
+import org.apache.horn.core.RecurrentLayeredNeuralNetwork;
+import org.apache.horn.core.Synapse;
+import org.apache.horn.funcs.CrossEntropy;
+import org.apache.horn.funcs.ReLU;
+import org.apache.horn.funcs.SoftMax;
+import org.apache.horn.funcs.Tanh;
+
+public class MnistRecurrentMultiLayerPerceptron {
+
+  public static class StandardNeuron extends
+      Neuron<Synapse<FloatWritable, FloatWritable>> {
+
+    @Override
+    public void forward(Iterable<Synapse<FloatWritable, FloatWritable>> messages)
+        throws IOException {
+      float sum = 0;
+      for (Synapse<FloatWritable, FloatWritable> m : messages) {
+        sum += m.getInput() * m.getWeight();
+      }
+      this.feedforward(squashingFunction.apply(sum));
+    }
+
+    @Override
+    public void backward(
+        Iterable<Synapse<FloatWritable, FloatWritable>> messages)
+        throws IOException {
+      float delta = 0;
+
+      if (!this.isDropped()) {
+        for (Synapse<FloatWritable, FloatWritable> m : messages) {
+          // Calculates error gradient for each neuron
+          delta += (m.getDelta() * m.getWeight());
+
+          // Weight corrections
+          float weight = -this.getLearningRate() * m.getDelta()
+              * this.getOutput() + this.getMomentumWeight() * m.getPrevWeight();
+          this.push(weight);
+        }
+      }
+      this.backpropagate(delta * squashingFunction.applyDerivative(getOutput()));
+    }
+  }
+
+  public static HornJob createJob(HamaConfiguration conf, String modelPath,
+      String inputPath, float learningRate, float momemtumWeight,
+      float regularizationWeight, int features, int hu, int labels,
+      int stepSize, int numOutCells, int miniBatch, int maxIteration)
+          throws IOException, InstantiationException, IllegalAccessException {
+
+    boolean isRecurrent = (stepSize == 1 ? false: true);
+
+    HornJob job = new HornJob(
+        conf, RecurrentLayeredNeuralNetwork.class, MnistRecurrentMultiLayerPerceptron.class);
+    job.setTrainingSetPath(inputPath);
+    job.setModelPath(modelPath);
+
+    job.setMaxIteration(maxIteration);
+    job.setLearningRate(learningRate);
+    job.setMomentumWeight(momemtumWeight);
+    job.setRegularizationWeight(regularizationWeight);
+
+    job.setConvergenceCheckInterval(10);
+    job.setBatchSize(miniBatch);
+    job.setRecurrentStepSize(stepSize);
+
+    job.setTrainingMethod(TrainingMethod.GRADIENT_DESCENT);
+
+    job.inputLayer(features, 1.0f); // droprate
+    job.addLayer(hu, Tanh.class, RecurrentDropoutNeuron.class, isRecurrent);
+    job.outputLayer(labels, SoftMax.class, RecurrentDropoutNeuron.class, numOutCells);
+
+    job.setCostFunction(CrossEntropy.class);
+
+    return job;
+  }
+
+  public static void main(String[] args) throws IOException,
+      InterruptedException, ClassNotFoundException,
+      NumberFormatException, InstantiationException, IllegalAccessException {
+
+    if (args.length < 9) {
+      System.out.println("Usage: <MODEL_PATH> <INPUT_PATH> "
+          + "<LEARNING_RATE> <MOMEMTUM_WEIGHT> <REGULARIZATION_WEIGHT> "
+          + "<FEATURE_DIMENSION> <HIDDEN_UNITS> <LABEL_DIMENSION> "
+          + "<STEP_SIZE> <NUM_OUTPUTCELLS> <BATCH_SIZE> <MAX_ITERATION>");
+      System.out.println("E.g. MnistRecurrentMultiLayerPerceptron"
+          + " ./model_rnn mnist.seq 0.01 0.9 0.0005 196 200 10 4 1 10 12000");
+      System.exit(-1);
+    }
+
+    HornJob rnn = createJob(new HamaConfiguration(), args[0], args[1],
+        Float.parseFloat(args[2]), Float.parseFloat(args[3]),
+        Float.parseFloat(args[4]), Integer.parseInt(args[5]),
+        Integer.parseInt(args[6]), Integer.parseInt(args[7]),
+        Integer.parseInt(args[8]), Integer.parseInt(args[9]),
+        Integer.parseInt(args[10]), Integer.parseInt(args[11]));
+
+    long startTime = System.currentTimeMillis();
+    if (rnn.waitForCompletion(true)) {
+      System.out.println("Optimization Finished! "
+          + (System.currentTimeMillis() - startTime) / 1000.0 + " seconds");
+    }
+  }
+}
diff --git a/src/main/java/org/apache/horn/examples/MultiLayerPerceptron.java b/src/main/java/org/apache/horn/examples/MultiLayerPerceptron.java
index 2c45673..e7f2683 100644
--- a/src/main/java/org/apache/horn/examples/MultiLayerPerceptron.java
+++ b/src/main/java/org/apache/horn/examples/MultiLayerPerceptron.java
@@ -22,6 +22,7 @@
 import org.apache.hadoop.io.FloatWritable;
 import org.apache.hama.HamaConfiguration;
 import org.apache.horn.core.Constants.TrainingMethod;
+import org.apache.horn.core.DropoutNeuron;
 import org.apache.horn.core.HornJob;
 import org.apache.horn.core.LayeredNeuralNetwork;
 import org.apache.horn.core.Neuron;
diff --git a/src/main/java/org/apache/horn/utils/ExclusiveOrConverter.java b/src/main/java/org/apache/horn/utils/ExclusiveOrConverter.java
new file mode 100644
index 0000000..9305875
--- /dev/null
+++ b/src/main/java/org/apache/horn/utils/ExclusiveOrConverter.java
@@ -0,0 +1,57 @@
+/**
+ * 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.
+ */
+package org.apache.horn.utils;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.io.LongWritable;
+import org.apache.hadoop.io.SequenceFile;
+import org.apache.hama.HamaConfiguration;
+import org.apache.hama.commons.io.FloatVectorWritable;
+import org.apache.hama.commons.math.DenseFloatVector;
+
+public class ExclusiveOrConverter {
+
+  private static int STEPS = 2;
+  private static int LABELS = 2;
+
+  public static void main(String[] args) throws Exception {
+
+    HamaConfiguration conf = new HamaConfiguration();
+    conf.set("dfs.block.size", "11554432");
+    FileSystem fs = FileSystem.get(conf);
+    String output = "semi.seq";
+    
+    @SuppressWarnings("deprecation")
+    SequenceFile.Writer writer = new SequenceFile.Writer(fs, conf, new Path(
+        output), LongWritable.class, FloatVectorWritable.class);
+
+    float[][] vals = {{0,0,1,0},{0,1,0,1},{1,0,0,1},{1,1,1,0}};
+
+    for (int i = 0; i < vals.length; i++) {
+      writer.append(new LongWritable(), new FloatVectorWritable(
+          new DenseFloatVector(vals[i])));
+    }
+    writer.close();
+  }
+
+}