从程序员的角度设计Java中的神经网络


用Java或任何其他编程语言设计神经网络需要了解人工神经网络的结构和功能。

人工神经网络像专家一样可以执行诸如模式识别,从数据中学习和预测趋势之类的任务,这与需要执行一组步骤以实现定义目标的常规算法相反。人工神经网络由于其高度互连的网络结构而可以学习如何独自解决某些任务。

人工神经元具有与人脑神经元相似的结构。天然神经元由细胞核,树突和轴突组成。轴突将自身延伸到几个分支,以与其他神经元的树突形成突触。

到目前为止,我们已经确定了神经元的结构和连接的神经元的网络。另一个重要方面是分别与具有单个神经元的神经网络相关联的处理或计算。天然神经元是信号处理器-它们在树突中接收微信号,这些微信号可以触发轴突中的信号。有一个阈值电位,当达到阈值电位时,它将激发轴突并将信号传播到其他神经元。因此,我们可以认为人造神经元在输入端具有信号收集器,在输出端具有激活单元,可以触发一个信号,该信号将被转发到其他神经元,类似于图片所示:

artificialneuron.jpg

此外,神经元之间的连接具有可以修改信号的关联权重,从而影响神经元的输出。由于权重在神经网络内部并且影响其输出,因此可以将它们视为神经网络的内部知识。调整表征神经元与其他神经元或外部世界的连接的权重将反映神经网络的功能。

如Bioinfo出版物所述:

人工神经元接收一个或多个输入(代表树突)并将它们加总以产生输出/ 激活 (代表神经元的轴突)。通常,对每个节点的总和进行加权,然后使总和通过激活函数或传递函数。 该组件将非线性添加到神经网络处理中,这是必需的,因为自然神经元具有非线性行为。在某些特殊情况下,它可以是线性函数。

根据维基百科:

可以将标准计算机芯片电路视为激活功能的数字网络,根据输入的不同,激活功能可以是“ ON”(1)或“ OFF”(0)。这类似于线性感知器在神经网络中的行为。但是,正是 非线性 激活函数允许此类网络仅使用少量节点来计算非平凡问题。常用的激活函数示例为Sigmoid,双曲正切,硬极限阈值和纯线性。

将这些知识转换为Java代码后,我们将获得一个神经元类,如下所示:

import java.util.ArrayList;
import java.util.List;

import edu.neuralnet.core.activation.ActivationFunction;
import edu.neuralnet.core.input.InputSummingFunction;

/**
 * Represents a neuron model comprised of: </br>
 * <ul>
 * <li>Summing part  - input summing function</li>
 * <li>Activation function</li>
 * <li>Input connections</li>
 * <li>Output connections</li>
 * </ul>
 */

public class Neuron {

 /**
  * Neuron's identifier
  */
 private String id;

 /**
  * Collection of neuron's input connections (connections to this neuron)
  */
 protected List < Connection > inputConnections;

 /**
  * Collection of neuron's output connections (connections from this to other
  * neurons)
  */
 protected List < Connection > outputConnections;

 /**
  * Input summing function for this neuron
  */
 protected InputSummingFunction inputSummingFunction;

 /**
  * Activation function for this neuron
  */
 protected ActivationFunction activationFunction;

 /**
  * Default constructor
  */
 public Neuron() {
  this.inputConnections = new ArrayList < > ();
  this.outputConnections = new ArrayList < > ();
 }

 /**
  * Calculates the neuron's output
  */
 public double calculateOutput() {
   double totalInput = inputSummingFunction.getOutput(inputConnections);

   return activationFunction.getOutput(totalInput);
  }
  ...
}

神经元具有输入和输出连接,输入求和和激活函数,但是输入权重在哪里?它们包含在连接本身中,如下所示:

/**
 * Represents a connection between two neurons an the associated weight.
 */
public class NeuronsConnection {

/**
 * From neuron for this connection (source neuron). This connection is
 * output connection for from neuron.
 */
protected Neuron fromNeuron;

/**
 * To neuron for this connection (target, destination neuron) This
 * connection is input connection for to neuron.
 */
protected Neuron toNeuron;

/**
 * Connection weight
 */
protected double weight;

/**
 * Creates a new connection between specified neurons with random weight.
 *
 * @param fromNeuron
 *            neuron to connect from
 * @param toNeuron
 *            neuron to connect to
 */
public NeuronsConnection(Neuron fromNeuron, Neuron toNeuron) {
this.fromNeuron = fromNeuron;
this.toNeuron = toNeuron;
this.weight = Math.random();
}

/**
 * Creates a new connection to specified neuron with specified weight object
 *
 * @param fromNeuron
 *            neuron to connect from
 * @param toNeuron
 *            neuron to connect to
 * @param weight
 *            weight for this connection
 */
public NeuronsConnection(Neuron fromNeuron, Neuron toNeuron, double weight) {
this(fromNeuron, toNeuron);
this.weight = weight;
}

/**
 * Returns weight for this connection
 *
 * @return weight for this connection
 */
public double getWeight() {
return weight;
}

/**
 * Set the weight of the connection.
 * 
 * @param weight
 *            The new weight of the connection to be set
 */
public void setWeight(double weight) {
this.weight = weight;
}

/**
 * Returns input of this connection - the activation function result
 * calculated in the input neuron of this connection.
 *
 * @return input received through this connection
 */
public double getInput() {
return fromNeuron.calculateOutput();
}

/**
 * Returns the weighted input of this connection
 *
 * @return weighted input of the connection
 */
public double getWeightedInput() {
return fromNeuron.calculateOutput() * weight;
}

/**
 * Gets from neuron for this connection
 * 
 * @return from neuron for this connection
 */
public Neuron getFromNeuron() {
return fromNeuron;
}

/**
 * Gets to neuron for this connection
 * 
 * @return neuron to set as to neuron
 */
public Neuron getToNeuron() {
return toNeuron;
}
...
}

连接对象提供权重,并负责计算加权输入。

输入求和函数和激活函数定义为接口,以便能够替换神经元的计算策略:

import java.util.List;

import edu.neuralnet.core.Connection;

/**
 * Represents the inputs summing part of a neuron also called signal collector.
 */
public interface InputSummingFunction {

/**
 * Performs calculations based on the output values of the input neurons.
 * 
 * @param inputConnections
 *            neuron's input connections
 * @return total input for the neuron having the input connections
 */
double collectOutput(List<Connection> inputConnections);
}

并分别实现:

import java.util.List;
import edu.neuralnet.core.Connection;

/**
 * Calculates the weighted sums of the input neurons' outputs.
 */
public final class WeightedSumFunction implements InputSummingFunction {

/**
 * {@inheritDoc}
 */
@Override
public double collectOutput(List<Connection> inputConnections) {
double weightedSum = 0d;
for (Connection connection : inputConnections) {
weightedSum += connection.getWeightedInput();
}

return weightedSum;
}
}

对于激活功能,接口可以定义如下:

/**
 * Neural networks activation function interface.
 */
public interface ActivationFunction {

/**
 * Performs calculation based on the sum of input neurons output.
 * 
 * @param summedInput
 *            neuron's sum of outputs respectively inputs for the connected
 *            neuron
 * 
 * @return Output's calculation based on the sum of inputs
 */
double calculateOutput(double summedInput);
}

在开始编写代码之前,需要注意的最后一个方面是神经网络层。神经网络可以由几个链接的层组成,形成所谓的多层网络。神经层可以分为三类:

  1. 输入层

  2. 隐藏层

  3. 输出层

在实践中,额外的神经层增加了外部刺激的另一个抽象层次,从而增强了神经网络表示更复杂知识的能力。

层类可以定义为具有联系的神经元列表:

import java.util.ArrayList;
import java.util.List;

/**
 * Neural networks can be composed of several linked layers, forming the
 * so-called multilayer networks. A layer can be defined as a set of neurons
 * comprising a single neural net's layer.
 */
public class NeuralNetLayer {

/**
 * Layer's identifier
 */
private String id;

/**
 * Collection of neurons in this layer
 */
protected List<Neuron> neurons;

/**
 * Creates an empty layer with an id.
  * @param id
 *            layer's identifier
 */
public NeuralNetLayer(String id) {
this.id = id;
neurons = new ArrayList<>();
}

/**
 * Creates a layer with a list of neurons and an id.
 *
 * @param id
 *            layer's identifier
 * @param neurons
 *            list of neurons to be added to the layer
 */
public NeuralNetLayer(String id, List<Neuron> neurons) {
this.id = id;
this.neurons = neurons;
}
...
}

最后,用Java创建的带有神经元层的简单神经网络:

/**
 * Represents an artificial neural network with layers containing neurons.
 */
public class NeuralNet {

/**
 * Neural network id
 */
private String id;

/**
 * Neural network input layer
 */
private NeuralNetLayer inputLayer;

/**
 * Neural network hidden layers
 */
private List<NeuralNetLayer> hiddenLayers;

/**
 * Neural network output layer
 */
private NeuralNetLayer outputLayer;

/**
 * Constructs a neural net with all layers present.
 * 
 * @param id
 *            Neural network id to be set
 * @param inputLayer
 *            Neural network input layer to be set
 * @param hiddenLayers
 *            Neural network hidden layers to be set
 * @param outputLayer
 *            Neural network output layer to be set
 */
public NeuralNet(String id, NeuralNetLayer inputLayer, List<NeuralNetLayer> hiddenLayers,
NeuralNetLayer outputLayer) {
this.id = id;
this.inputLayer = inputLayer;
this.hiddenLayers = hiddenLayers;
this.outputLayer = outputLayer;
}

/**
 * Constructs a neural net without hidden layers.
 * 
 * @param id
 *            Neural network id to be set
 * @param inputLayer
 *            Neural network input layer to be set
 * @param outputLayer
 *            Neural network output layer to be set
 */
public NeuralNet(String id, NeuralNetLayer inputLayer, NeuralNetLayer outputLayer) {
this.id = id;
this.inputLayer = inputLayer;
this.outputLayer = outputLayer;
}
...
}

我们所获得的是具有层,神经元和连接的基于Java的神经网络的结构定义。我们还讨论了一些激活功能,并为它们定义了一个接口。为简单起见,我们省略了各种激活函数的实现以及学习神经网络的基础知识。这两个主题将在本系列的后续文章中介绍。


原文链接:https://codingdict.com/