手把手教你使用Java实现一个神经网络
首先看一下运行效果:
下面是项目整体目录:
0.实现神经网络总览
神经网络由层、神经元、权重、激活函数和偏置组成。每层都有一个或者多个神经元,每一个神经元都和神经输入/输出连接,这些连接就是权重。
需要重点强调一下,一个神经网络可能有很多隐含层,也可能一个没有,因为每层的神经元数目也可能不同。然而,输入输出层的神经元个数分别等于神经输入/输出的个数。
我们为了实现,需要定义以下的类:
- Neuron: 定义人工神经元
- NeuralLayer: 抽象类,定义一个神经元层。
- InputLayer: 定义神经输入层
- HiddenLayer:定义输入层和输出层之间的层
- OutputLayer: 定义神经输出层。
- InputNeuron: 定义神经网络输入中出现的神经元。
- NeuralNet:将前面定义的所有类组成一个ANN结构。
除了以上的类,我们还需要为激活函数定义一个IActivationFunction接口。这是必要的,因为激活函数与方法类似,需要作为神经元的一个属性进行分配。所以要为激活函数定义类,这些类需要实现IActivationFunction接口:
- Linear
- Sigmoid
- Step
- HyperTan
第一章的编码基本完成。除此之外,还需要定义俩个类。一个用于异常处理(NeuralException),另一个用于产生随机数(RandomNumberGenerator)。最后,将这些类分别放到俩个包。
1.神经元Neuron类
神经元类是本章代码的基础类。根据理论,人工神经元有如下属性
- 输入
- 权重
- 偏置
- 激活函数
- 输出
首先定义神经元的各种属性:
public class Neuron {
//神经元相关的权重
protected ArrayList<Double> weight;
//神经元的输入
private ArrayList<Double> input;
//这个神经元的输出,由激活函数产生
private Double output;
//传递给激活函数的值
private Double outputBeforeActivation;
//输入的数量。如果为0,则表示神经元尚未初始化。
private int numberOfInputs = 0;
//神经元的偏差。除了第一层,其他都应该是1.0。
protected Double bias = 1.0;
//神经元的激活函数
private IActivationFunction activationFunction;
}
当实例化神经元时,需要指定输入数据的个数以及激活函数。构造函数如下:
public Neuron(int numberofinputs,IActivationFunction iaf){
numberOfInputs=numberofinputs;
weight=new ArrayList<>(numberofinputs+1);
input=new ArrayList<>(numberofinputs);
activationFunction=iaf;
}
注意,为偏置定义另一个权重。一个重要的步骤是初始化神经元,也就是为权重赋初始值。这主要在init()方法中完成,通过随机数生成器静态类RandomNumberGenerator生成随机数,赋值权重。注意:设置权重值时需要防止权重数组越界。
public void init(){
if(numberOfInputs>0){
for(int i=0;i<=numberOfInputs;i++){
double newWeight = RandomNumberGenerator.GenerateNext();
try{
this.weight.set(i, newWeight);
}
catch(IndexOutOfBoundsException iobe){
this.weight.add(newWeight);
}
}
}
}
最后,在calc()方法中计算输出值:
public void calc(){
outputBeforeActivation=0.0;
if(numberOfInputs>0){
if(input!=null && weight!=null){
for(int i=0;i<=numberOfInputs;i++){
outputBeforeActivation+=(i==numberOfInputs?bias:input.get(i))*weight.get(i);
}
}
}
output=activationFunction.calc(outputBeforeActivation);
}
首先需要对所有输入和权重的成绩进行求和(偏置乘最后一个权重,i==Number-OfInputs),然后将得出的结果保存在属性outputBeforeActivation中。激活函数用这个值计算神经元的输出。
总代码如下:
package neuralnet;
import java.util.ArrayList;
public class Neuron {
//神经元相关的权重
protected ArrayList<Double> weight;
//神经元的输入
private ArrayList<Double> input;
//这个神经元的输出,由激活函数产生
private Double output;
//传递给激活函数的值
private Double outputBeforeActivation;
//输入的数量。如果为0,则表示神经元尚未初始化。
private int numberOfInputs = 0;
//神经元的偏差。除了第一层,其他都应该是1.0。
protected Double bias = 1.0;
//神经元的激活函数
private IActivationFunction activationFunction;
public Neuron(){
}
public Neuron(int numberofinputs){
numberOfInputs=numberofinputs;
weight=new ArrayList<>(numberofinputs+1);
input=new ArrayList<>(numberofinputs);
}
public Neuron(int numberofinputs,IActivationFunction iaf){
numberOfInputs=numberofinputs;
weight=new ArrayList<>(numberofinputs+1);
input=new ArrayList<>(numberofinputs);
activationFunction=iaf;
}
public void init(){
if(numberOfInputs>0){
for(int i=0;i<=numberOfInputs;i++){
double newWeight = RandomNumberGenerator.GenerateNext();
try{
this.weight.set(i, newWeight);
}
catch(IndexOutOfBoundsException iobe){
this.weight.add(newWeight);
}
}
}
}
public void setInputs(double [] values){
if(values.length==numberOfInputs){
for(int i=0;i<numberOfInputs;i++){
try{
input.set(i, values[i]);
}
catch(IndexOutOfBoundsException iobe){
input.add(values[i]);
}
}
}
}
public void setInputs(ArrayList<Double> values){
if(values.size()==numberOfInputs){
input=values;
}
}
public ArrayList<Double> getArrayInputs(){
return input;
}
public double[] getInputs(){
double[] inputs = new double[numberOfInputs];
for (int i=0;i<numberOfInputs;i++){
inputs[i]=this.input.get(i);
}
return inputs;
}
public void setInput(int i,double value){
if(i>=0 && i<numberOfInputs){
try{
input.set(i, value);
}
catch(IndexOutOfBoundsException iobe){
input.add(value);
}
}
}
public double getInput(int i){
return input.get(i);
}
public double[] getWeights(){
double[] weights = new double[numberOfInputs+1];
for(int i=0;i<=numberOfInputs;i++){
weights[i]=weight.get(i);
}
return weights;
}
public ArrayList<Double> getArrayWeights(){
return weight;
}
public void updateWeight(int i, double value){
if(i>=0 && i<=numberOfInputs){
weight.set(i, value);
}
}
public int getNumberOfInputs(){
return this.numberOfInputs;
}
public void setWeight(int i,double value) throws NeuralException{
if(i>=0 && i<numberOfInputs){
this.weight.set(i, value);
}
else{
throw new NeuralException("Invalid weight index");
}
}
public double getOutput(){
return output;
}
public void calc(){
outputBeforeActivation=0.0;
if(numberOfInputs>0){
if(input!=null && weight!=null){
for(int i=0;i<=numberOfInputs;i++){
outputBeforeActivation+=(i==numberOfInputs?bias:input.get(i))*weight.get(i);
}
}
}
output=activationFunction.calc(outputBeforeActivation);
}
public void setActivationFunction(IActivationFunction iaf){
this.activationFunction=iaf;
}
public double getOutputBeforeActivation(){
return outputBeforeActivation;
}
}
2.NeuralLayer类
在这个类中,将把在同一层中对齐的神经元分成一组。因为一层需要将值传递给另一层,也需要定义层与层之间的连接。类的属性定义如下:
//这一层的神经元数量
protected int numberOfNeuronsInLayer;
//这一层的神经元
private ArrayList<Neuron> neuron;
//激励函数
protected IActivationFunction activationFnc;
//将值提供给此层的前一层
protected NeuralLayer previousLayer;
protected NeuralLayer nextLayer;
protected ArrayList<Double> input;
protected ArrayList<Double> output;
protected int numberOfInputs;
这个类是抽象的,整整可实例化的层类是InputLayer、HiddenLayer和Outp-utLayer。创建一个类是,必须使用另一个类的构造函数,这几个类具有相似的构造函数。
而层的初始化和计算都和神经元一样,他们也实现了init()方法和calc() 方法。生命欸protected类型,确保了只有子类可以调用或覆盖这些方法。
全部的代码如下:
package neuralnet;
import java.util.ArrayList;
public abstract class NeuralLayer {
//这一层的神经元数量
protected int numberOfNeuronsInLayer;
//这一层的神经元
private ArrayList<Neuron> neuron;
//激励函数
protected IActivationFunction activationFnc;
//将值提供给此层的前一层
protected NeuralLayer previousLayer;
protected NeuralLayer nextLayer;
protected ArrayList<Double> input;
protected ArrayList<Double> output;
protected int numberOfInputs;
public NeuralLayer(int numberofneurons){
this.numberOfNeuronsInLayer=numberofneurons;
neuron = new ArrayList<>(numberofneurons);
output = new ArrayList<>(numberofneurons);
}
public NeuralLayer(int numberofneurons,IActivationFunction iaf){
this.numberOfNeuronsInLayer=numberofneurons;
this.activationFnc=iaf;
neuron = new ArrayList<>(numberofneurons);
output = new ArrayList<>(numberofneurons);
}
public int getNumberOfNeuronsInLayer(){
return numberOfNeuronsInLayer;
}
public ArrayList<Neuron> getListOfNeurons(){
return neuron;
}
protected NeuralLayer getPreviousLayer(){
return previousLayer;
}
protected NeuralLayer getNextLayer(){
return nextLayer;
}
protected void setPreviousLayer(NeuralLayer layer){
previousLayer=layer;
}
protected void setNextLayer(NeuralLayer layer){
nextLayer=layer;
}
protected void init(){
if(numberOfNeuronsInLayer>=0){
for(int i=0;i<numberOfNeuronsInLayer;i++){
try{
neuron.get(i).setActivationFunction(activationFnc);
neuron.get(i).init();
}
catch(IndexOutOfBoundsException iobe){
neuron.add(new Neuron(numberOfInputs,activationFnc));
neuron.get(i).init();
}
}
}
}
protected void setInputs(ArrayList<Double> inputs){
this.numberOfInputs=inputs.size();
this.input=inputs;
}
protected void calc(){
if(input!=null && neuron!=null){
for(int i=0;i<numberOfNeuronsInLayer;i++){
neuron.get(i).setInputs(this.input);
neuron.get(i).calc();
try{
output.set(i,neuron.get(i).getOutput());
}
catch(IndexOutOfBoundsException iobe){
output.add(neuron.get(i).getOutput());
}
}
}
}
protected ArrayList<Double> getOutputs(){
return output;
}
protected Neuron getNeuron(int i){
return neuron.get(i);
}
protected void setNeuron(int i, Neuron _neuron){
try{
this.neuron.set(i, _neuron);
}
catch(IndexOutOfBoundsException iobe){
this.neuron.add(_neuron);
}
}
}
3.ActivationFunction接口
在定义NeerualNetwork类之前,先看接口的Java代码示例:
public interface IActivationFunction {
double calc(double x);
public enum ActivationFunctionENUM {
STEP, LINEAR, SIGMOID, HYPERTAN
}
}
其中calc()方法属于实现IActivationFunction接口的特定的激活函数类,例如Sigmoid函数。
public class Sigmoid implements IActivationFunction {
private double a=1.0;
public Sigmoid(){
}
public Sigmoid(double value){
this.setA(value);
}
public void setA(double value){
this.a=value;
}
@Override
public double calc(double x){
return 1.0/(1.0+Math.exp(-a*x));
}
}
这也是多态性的一个示例,即在相同的函数名下,类和方法呈现不同的行为,产生灵活的应用。
4.神经网络(NeuralNet)类
最后,定义神经网络类。到目前为止,我们已经知道,神经网络在神经层中组织神经元,且每个神经网络至少有俩层,一个用来接收收入,一个用来处理输出,还有一个数量可变的隐含层。因此,除了具有和神经元以及NeuralLary类相似的属性之外,Neural还将拥有这几个属性,如numberOfInputs,numberOfOutputs等。
private InputLayer inputLayer;
private ArrayList<HiddenLayer> hiddenLayer;
private OutputLayer outputLayer;
private int numberOfHiddenLayers;
private int numberOfInputs;
private int numberOfOutputs;
private ArrayList<Double> input;
private ArrayList<Double> output;
这个类的构造函数比前面类的参数更多:
public NeuralNet(int numberofinputs,int numberofoutputs,
int [] numberofhiddenneurons,IActivationFunction[] hiddenAcFnc,
IActivationFunction outputAcFnc)
如果隐含层的数量是可变的,我们还应该考虑到可能有多个隐含层或0个隐含层,且对每个隐含层来说,隐藏神经元的数量也是可变的。处理这种可变性的最好方法就是把每个隐含层中的神经元数量表示为一个整数向量(参数 numberofhiddenlayers)。此外,需要为每个隐含层定义激活函数,包括输出层,完成这个目标所需要的参数分别为hiddenActivationFnc和outputAcFnc。
完整实现如下:
public NeuralNet(int numberofinputs,int numberofoutputs,
int [] numberofhiddenneurons,IActivationFunction[] hiddenAcFnc,
IActivationFunction outputAcFnc){
numberOfHiddenLayers=numberofhiddenneurons.length;
numberOfInputs=numberofinputs;
numberOfOutputs=numberofoutputs;
if(numberOfHiddenLayers==hiddenAcFnc.length){
input=new ArrayList<>(numberofinputs);
inputLayer=new InputLayer(numberofinputs);
if(numberOfHiddenLayers>0){
hiddenLayer=new ArrayList<>(numberOfHiddenLayers);
}
for(int i=0;i<numberOfHiddenLayers;i++){
if(i==0){
try{
hiddenLayer.set(i,new HiddenLayer(numberofhiddenneurons[i],
hiddenAcFnc[i],
inputLayer.getNumberOfNeuronsInLayer()));
}
catch(IndexOutOfBoundsException iobe){
hiddenLayer.add(new HiddenLayer(numberofhiddenneurons[i],
hiddenAcFnc[i],
inputLayer.getNumberOfNeuronsInLayer()));
}
inputLayer.setNextLayer(hiddenLayer.get(i));
}
else{
try{
hiddenLayer.set(i, new HiddenLayer(numberofhiddenneurons[i],
hiddenAcFnc[i],hiddenLayer.get(i-1)
.getNumberOfNeuronsInLayer()
));
}
catch(IndexOutOfBoundsException iobe){
hiddenLayer.add(new HiddenLayer(numberofhiddenneurons[i],
hiddenAcFnc[i],hiddenLayer.get(i-1)
.getNumberOfNeuronsInLayer()
));
}
hiddenLayer.get(i-1).setNextLayer(hiddenLayer.get(i));
}
}
if(numberOfHiddenLayers>0){
outputLayer=new OutputLayer(numberofoutputs,outputAcFnc,
hiddenLayer.get(numberOfHiddenLayers-1)
.getNumberOfNeuronsInLayer()
);
hiddenLayer.get(numberOfHiddenLayers-1).setNextLayer(outputLayer);
}
else{
outputLayer=new OutputLayer(numberofinputs, outputAcFnc,
numberofoutputs);
inputLayer.setNextLayer(outputLayer);
}
}
}
5.运行程序
代码如下:
package neuralnet;
import neuralnet.math.IActivationFunction;
import neuralnet.math.Linear;
import neuralnet.math.RandomNumberGenerator;
import neuralnet.math.Sigmoid;
public class NeuralNetConsoleTest {
public static void main(String[] args){
RandomNumberGenerator.seed=0;
int numberOfInputs=2;
int numberOfOutputs=1;
int[] numberOfHiddenNeurons= { 3 };
IActivationFunction[] hiddenAcFnc = { new Sigmoid(1.0) } ;
Linear outputAcFnc = new Linear(1.0);
System.out.println("Creating Neural Netword...");
NeuralNet nn = new NeuralNet(numberOfInputs,numberOfOutputs,
numberOfHiddenNeurons,hiddenAcFnc,outputAcFnc);
System.out.println("Neural Network Network...");
double [] neuralInput = { 1.5 , 0.5 };
System.out.println("Feeding the values {1.5;0.5} to the neural network");
double [] neuralOutput;
nn.setInputs(neuralInput);
nn.calc();
neuralOutput=nn.getOutputs();
System.out.println("OutPut 1:" + neuralOutput[0]);
neuralInput[0] = 1.0;
neuralInput[1] = 2.1;
System.out.println("Feeding the values {1.0;2.1} to the neural network");
nn.setInputs(neuralInput);
nn.calc();
neuralOutput=nn.getOutputs();
System.out.println("OutPut 2:" + neuralOutput[0]);
}
}
到此就完成了我们神经网络的全部代码:下面是源代码压缩包。有需要的同学可以下载运行。
手把手教你使用Java实现一个神经网络的更多相关文章
- 手把手教你用redis实现一个简单的mq消息队列(java)
众所周知,消息队列是应用系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构.目前使用较多的消息队列有 ActiveMQ,RabbitMQ,Zero ...
- 手把手教你使用FineUI开发一个b/s结构的取送货管理信息系统系列博文索引
近阶段接到一些b/s类型的软件项目,但是团队成员之前大部分没有这方面的开发经验,于是自己选择了一套目前网上比较容易上手的开发框架(FineUI),计划录制一套视频讲座,来讲解如何利用FineUI快速开 ...
- 手把手教你用vue-cli构建一个简单的路由应用
上一章说道:十分钟上手-搭建vue开发环境(新手教程)https://www.jianshu.com/p/0c6678671635 开发环境搭建好之后,那么开始新添加一些页面,构建最基本的vue项目, ...
- 手把手教你使用 js 实现一个 Canvas 编辑器
手把手教你使用 js 实现一个 Canvas 编辑器 拖拽 缩放,等比缩放 导出 image 模版 撤销,重做 OOP,封装,继承,多态 发布库 CI/CD (gitlab/github) ... h ...
- 手把手教你从零写一个简单的 VUE
本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...
- 手把手教你从零写一个简单的 VUE--模板篇
教程目录1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 Hello,我又回来了,上一次的文章教会了大家如何书写一个简单 VUE,里面实现了VUE 的数据驱动视图 ...
- 【良心保姆级教程】java手把手教你用swing写一个学生的增删改查模块
很多刚入门的同学,不清楚如何用java.swing去开发出一个系统? 不清楚如何使用java代码去操作数据库进行增删改查一些列操作,不清楚java代码和数据库(mysql.sqlserver)之间怎么 ...
- 手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏01游戏窗口
项目源码 项目源码 游戏配置信息类 Config.java 没什么解释的. package config; public class Config { public final static Stri ...
- Kaggle系列1:手把手教你用tensorflow建立卷积神经网络实现猫狗图像分类
去年研一的时候想做kaggle上的一道题目:猫狗分类,但是苦于对卷积神经网络一直没有很好的认识,现在把这篇文章的内容补上去.(部分代码参考网上的,我改变了卷积神经网络的网络结构,其实主要部分我加了一层 ...
随机推荐
- 开源音乐下载神器XMusicDownloader更新,支持歌单一键下载,支持无损音乐
开源音乐下载神器XMusicDownloader更新啦,新增网易.腾讯音乐歌单歌曲.歌手歌曲.专辑歌曲一键下载,同时支持下载flac无损音乐. 功能 V1.0 功能开源工具软件XMusicDownlo ...
- 敏捷开发--必备工具Jira&Confluence学习视频
敏捷开发必备工具:Jira+confluence,完美组合. 入门培训视频,内含Jira, Confluence, BigGantt, Zephyr, Tempo, Question, ScriptR ...
- JavaScript算法实现——排序
在计算机编程中,排序算法是最常用的算法之一,本文介绍了几种常见的排序算法以及它们之间的差异和复杂度. 冒泡排序 冒泡排序应该是最简单的排序算法了,在所有讲解计算机编程和数据结构的课程中,无一例外都会拿 ...
- 只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常
## 统一返回值 在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生. 比较通用的返回值格式如下: ```jav ...
- Azure Devops: COPY failed: stat /var/lib/docker/tmp/docker-builder268095359/xxxxxxx.csproj no such file or directory
在Azure Devops中部署docker镜像时, 出现COPY failed: stat /var/lib/docker/tmp/docker-builder268095359/xxxxxxx. ...
- Codeforces 1004E
题意略. 思路: 这k个点应该放在这棵树的直径上,并且能连成一条路径.如果k比树的直径上的点要多,那么我们就不用把这k个点都用上, 只需要把这棵树直径上所有的点都覆盖上就行了.如果k比树的直径上的点要 ...
- linux安装redis详细步骤(系统centos 6.4 )
1.安装redis 进入安装目录下载: cd /usr/local/redis wget http://download.redis.io/releases/redis-3.0.7.tar.gz 解 ...
- 基于JRebel开发的MySQL Explain插件
前言 我们在使用数据库时,为了使业务系统性能达到最优,往往都需要避免慢SQL查询,不能等到线上告警了再排查是否为慢SQL导致.在开发阶段,每个开发人员就应该针对自己写的SQL看是否可能为慢SQL,从而 ...
- 【Leetcode】【简单】【17. 整数反转】【JavaScript】
题目描述 7. 整数反转 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例 1: 输入: 123输出: 321 示例 2: 输入: -123输出: -321示例 3: 输 ...
- 用故事解析setTimeout和setInterval(内含js单线程和任务队列)
区别: setTimeout(fn,t): 延迟调用,超过了时间就调用回调函数,返回一个id,使用clearTimeout(id)取消执行. 注意:取消了里面的回调函数就不执行了哦,而不是取消的时候就 ...