Anny on

Source: Layer.js

import _ from 'lodash'
import INITIALIZE from './Initialize'
import Neuron from './Neuron'

/**
 * @class
 *   Layers are collections of [Neurons]{@link Neuron}.  They can do all the
 *   things Neurons can do by invoking methods on all the Neurons in the Layer.
 *
 *   Layers are organized into a {@link Network}
 * @see {Neuron}
 */
class Layer {
  /**
   * Creates a single dimension Layer of [Neurons]{@link Neuron}.
   * @param {number} size - The number of Neurons this Layer should have.
   * @param {number} [learningRate] - The learning rate passed directly to the
   *   Neuron constructor.
   * @param {object} [activation] - The activation function passed directly to
   *   the
   *   Neuron constructor.
   */
  constructor(size, activation, learningRate) {
    if (!_.isNumber(size)) {
      throw new Error(`Layer() 'size' must be a number, not: ${typeof size}`)
    }
    this.neurons = _.times(size, () => new Neuron(activation, learningRate))
  }

  /**
   * Connects every Neuron in this Layer to each Neuron in the `target` Layer.
   * @param {Layer} targetLayer - The Layer to connect to.
   */
  connect(targetLayer) {
    // if this Layer has no bias Neuron, add one
    // only Layers with outgoing connections get bias Neurons
    if (!_.some(this.neurons, 'isBias')) {
      const biasNeuron = new Neuron()
      biasNeuron.isBias = true
      this.neurons.push(biasNeuron)
    }

    _.forEach(this.neurons, (source) => {
      // every neuron in this Layer is connected to each neuron in the next.
      // we can assume the numInputs to be the num of neurons in this Layer.

      // connect each neuron in this Layer to every Neuron in the targetLayer
      _.forEach(targetLayer.neurons, (target) => {
        source.connect(target, INITIALIZE.weight(this.neurons.length))
      })
    })
  }

  /**
   * Activates all the Neurons in this Layer with the given array of values.
   * @param {number[]} [values=[]] - Map of input values for each Neuron.
   * @returns {number[]} - Array of Neuron output values.
   */
  activate(values = []) {
    /* eslint-disable lodash/prefer-invoke-map */
    // https://github.com/wix/eslint-plugin-lodash/issues/128
    return _.map(this.neurons, (neuron, i) => neuron.activate(values[i]))
    /* eslint-enable lodash/prefer-invoke-map */
  }

  /**
   * Sets all the Neuron `delta`s in this Layer to the given array of values.
   * @param {number[]} [deltas=[]] - Delta values, one for each Neuron.
   * @returns {number[]}
   */
  backprop(deltas = []) {
    _.forEach(this.neurons, (neuron, i) => neuron.backprop(deltas[i]))
  }

  /**
   * Calculate and accumulate Neuron Connection weight gradients.
   * Does not update weights. Useful during batch/mini-batch training.
   */
  accumulateGradients() {
    _.forEach(this.neurons, neuron => neuron.accumulateGradients())
  }

  /**
   * Update Neuron Connection weights and reset their accumulated gradients.
   */
  updateWeights() {
    _.forEach(this.neurons, neuron => neuron.updateWeights())
  }

  /**
   * Returns the number of Neurons in this Layer, excluding Bias Neurons.
   */
  size() {
    return _.filter(this.neurons, { isBias: false }).length
  }
}

export default Layer