Created
January 23, 2020 22:56
-
-
Save boxbeam/c28b3d5cabcd474ef460945021fea25d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package redempt.numberrecognition.ai; | |
import java.util.ArrayList; | |
import java.util.List; | |
public class Layer { | |
private List<Neuron> neurons = new ArrayList<>(); | |
public void setNextLayerSize(int size) { | |
for (Neuron neuron : neurons) { | |
neuron.generateWeights(size); | |
} | |
} | |
public Layer(int count) { | |
for (int i = 0; i < count; i++) { | |
neurons.add(new Neuron()); | |
} | |
} | |
public List<Neuron> getNeurons() { | |
return neurons; | |
} | |
public void addNeuron(Neuron neuron) { | |
neurons.add(neuron); | |
} | |
public Layer clone() { | |
List<Neuron> cloned = new ArrayList<>(); | |
neurons.forEach(n -> cloned.add(n.clone())); | |
Layer layer = new Layer(0); | |
layer.neurons = cloned; | |
return layer; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package redempt.numberrecognition.ai; | |
import java.util.ArrayList; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Set; | |
public class NeuralNetwork { | |
private List<Layer> layers = new ArrayList<>(); | |
public int score = -1; | |
private int[] layerStructure; | |
public NeuralNetwork(int... layerStructure) { | |
if (layerStructure.length <= 1) { | |
throw new IllegalArgumentException("Neural network must have at least 2 layers!"); | |
} | |
this.layerStructure = layerStructure; | |
for (int num : layerStructure) { | |
layers.add(new Layer(num)); | |
} | |
for (int i = 0; i < layers.size() - 1; i++) { | |
layers.get(i).setNextLayerSize(layers.get(i + 1).getNeurons().size()); | |
} | |
for (Neuron neuron : layers.get(layers.size() - 1).getNeurons()) { | |
neuron.weights = new double[] {1}; | |
neuron.bias = 0; | |
} | |
layers.get(layers.size() - 1).setNextLayerSize(1); | |
} | |
public double[] feed(double[]... inputs) { | |
int pos = 0; | |
for (double[] array : inputs) { | |
for (double num : array) { | |
layers.get(0).getNeurons().get(pos).feed(num); | |
pos++; | |
} | |
} | |
for (Neuron neuron : layers.get(0).getNeurons()) { | |
double[] output = neuron.output(); | |
neuron.reset(); | |
for (int i = 0; i < output.length; i++) { | |
layers.get(1).getNeurons().get(i).feed(output[i]); | |
} | |
} | |
for (int i = 1; i < layers.size() - 1; i++) { | |
for (Neuron neuron : layers.get(i).getNeurons()) { | |
double[] output = neuron.output(); | |
neuron.reset(); | |
for (int x = 0; x < output.length; x++) { | |
layers.get(i + 1).getNeurons().get(x).feed(output[x]); | |
} | |
} | |
} | |
double[] outputs = new double[layers.get(layers.size() - 1).getNeurons().size()]; | |
for (int i = 0; i < outputs.length; i++) { | |
Neuron neuron = layers.get(layers.size() - 1).getNeurons().get(i); | |
outputs[i] = neuron.output()[0]; | |
neuron.reset(); | |
} | |
return outputs; | |
} | |
public double[] dryFeed(double[]... inputs) { | |
int pos = 0; | |
for (double[] array : inputs) { | |
for (double num : array) { | |
layers.get(0).getNeurons().get(pos).feed(num); | |
pos++; | |
} | |
} | |
for (Neuron neuron : layers.get(0).getNeurons()) { | |
double[] output = neuron.output(); | |
for (int i = 0; i < output.length; i++) { | |
layers.get(1).getNeurons().get(i).feed(output[i]); | |
} | |
} | |
for (int i = 1; i < layers.size() - 1; i++) { | |
for (Neuron neuron : layers.get(i).getNeurons()) { | |
double[] output = neuron.output(); | |
for (int x = 0; x < output.length; x++) { | |
layers.get(i + 1).getNeurons().get(x).feed(output[x]); | |
} | |
} | |
} | |
double[] outputs = new double[layers.get(layers.size() - 1).getNeurons().size()]; | |
for (int i = 0; i < outputs.length; i++) { | |
Neuron neuron = layers.get(layers.size() - 1).getNeurons().get(i); | |
outputs[i] = neuron.output()[0]; | |
} | |
return outputs; | |
} | |
public double getError(double[][] samples, double[][] expected) { | |
double error = 0; | |
for (int x = 0; x < samples.length; x++) { | |
double[] input = samples[x]; | |
double[] output = feed(input); | |
for (int i = 0; i < output.length; i++) { | |
error += Math.abs(output[i] - expected[x][i]); | |
} | |
} | |
return error; | |
} | |
public void train(double[][] samples, double[][] expected, int iterations) { | |
double error = getError(samples, expected); | |
double[][] nudges = getAverageNudges(samples, expected); | |
List<NeuronLocation> locations = new ArrayList<>(); | |
int tx = -1; | |
int ty = -1; | |
double size = -1; | |
for (int x = 0; x < nudges.length; x++) { | |
for (int y = 0; y < nudges[x].length; y++) { | |
locations.add(new NeuronLocation(x, y)); | |
if (Math.abs(nudges[x][y]) > size) { | |
size = Math.abs(nudges[x][y]); | |
ty = y; | |
tx = x; | |
} | |
} | |
} | |
locations.sort((a, b) -> { | |
return (int) Math.signum(nudges[a.getLayer()][a.getNumber()] - nudges[b.getLayer()][b.getNumber()]); | |
}); | |
} | |
private void trainLayer(double[][] sample, double[][] expected, int l) { | |
Layer layer = layers.get(l); | |
dryFeed(sample); | |
List<Neuron> neurons = new ArrayList<>(layer.getNeurons()); | |
double maxVal = -1; | |
Neuron best = null; | |
for (Neuron neuron : neurons) { | |
double value = Math.abs(neuron.value); | |
for (double weight : neuron.weights) { | |
value += (1 - Math.abs(weight)) / neuron.weights.length; | |
} | |
if (value > maxVal || best == null) { | |
maxVal = value; | |
best = neuron; | |
} | |
} | |
resetNeurons(); | |
} | |
private double[][] getNudges(double[] expected) { | |
double[][] nudges = new double[layers.size()][]; | |
for (int i = 0; i < layers.size(); i++) { | |
nudges[i] = new double[layers.get(i).getNeurons().size()]; | |
} | |
for (int n = 0; n < layers.get(layers.size() - 1).getNeurons().size(); n++) { | |
Neuron neuron = layers.get(layers.size() - 1).getNeurons().get(n); | |
double diff = expected[n] - neuron.value; | |
nudges[layers.size() - 1][n] = diff; | |
} | |
for (int l = layers.size() - 2; l > 0; l--) { | |
Layer layer = layers.get(l); | |
for (int n = 0; n < layer.getNeurons().size(); n++) { | |
Neuron neuron = layer.getNeurons().get(n); | |
double nudge = 0; | |
for (int d = 0; d < nudges[l + 1].length; d++) { | |
double diff = nudges[l + 1][d]; | |
nudge += diff * neuron.weights[d]; | |
} | |
nudge /= nudges[l + 1].length; | |
nudges[l][n] = nudge; | |
} | |
} | |
return nudges; | |
} | |
private double[][] getAverageNudges(double[][] samples, double[][] expected) { | |
double[][] nudges = new double[layers.size()][]; | |
for (int i = 0; i < layers.size(); i++) { | |
nudges[i] = new double[layers.get(i).getNeurons().size()]; | |
} | |
for (int i = 0; i < samples.length; i++) { | |
resetNeurons(); | |
dryFeed(samples[i]); | |
nudges = add(nudges, getNudges(expected[i])); | |
} | |
nudges = divide(nudges, samples.length); | |
return nudges; | |
} | |
private double[][] add(double[][] first, double[][] second) { | |
double[][] result = new double[first.length][]; | |
for (int i = 0; i < result.length; i++) { | |
result[i] = new double[first[i].length]; | |
} | |
for (int x = 0; x < result.length; x++) { | |
for (int y = 0; y < result[x].length; y++) { | |
result[x][y] = first[x][y] + second[x][y]; | |
} | |
} | |
return result; | |
} | |
private double[][] divide(double[][] array, double divide) { | |
double[][] result = new double[array.length][]; | |
for (int i = 0; i < result.length; i++) { | |
result[i] = new double[array[i].length]; | |
} | |
for (int x = 0; x < result.length; x++) { | |
for (int y = 0; y < result[x].length; y++) { | |
result[x][y] = array[x][y] / divide; | |
} | |
} | |
return result; | |
} | |
private Set<NeuronLocation> getSignificantNeurons(NeuronLocation loc) { | |
Set<NeuronLocation> significant = new HashSet<>(); | |
for (int x = 1; x < loc.getLayer() - 1; x++) { | |
for (int y = 0; y < layers.get(x).getNeurons().size(); y++) { | |
significant.add(new NeuronLocation(x, y)); | |
} | |
} | |
return significant; | |
} | |
private double[] tweakAndCheck(NeuronLocation location, int weight) { | |
if (weight > -1) { | |
NeuralNetwork clone = clone(); | |
Neuron neuron = clone.getNeuron(location); | |
neuron.weights[weight] += 0.01; | |
return clone.dryFeed(getValues(0)); | |
} else { | |
NeuralNetwork clone = clone(); | |
Neuron neuron = clone.getNeuron(location); | |
neuron.bias += 0.01; | |
return clone.dryFeed(getValues(0)); | |
} | |
} | |
private double[] getValues(int layer) { | |
double[] values = new double[layers.get(layer).getNeurons().size()]; | |
for (int i = 0; i < values.length; i++) { | |
values[i] = layers.get(layer).getNeurons().get(i).value; | |
} | |
return values; | |
} | |
private double[] getOutputs() { | |
double[] values = new double[layers.get(layers.size() - 1).getNeurons().size()]; | |
for (int i = 0; i < values.length; i++) { | |
values[i] = layers.get(layers.size() - 1).getNeurons().get(i).output()[0]; | |
} | |
return values; | |
} | |
public Neuron getNeuron(NeuronLocation location) { | |
return layers.get(location.getLayer()).getNeurons().get(location.getNumber()); | |
} | |
public void resetNeurons() { | |
layers.stream().forEach(c -> c.getNeurons().stream().forEach(Neuron::reset)); | |
} | |
public void mutate(int iterations) { | |
for (int i = 0; i < iterations; i++) { | |
Layer layer = layers.get((int) Math.round(Math.random() * (layers.size() - 2))); | |
Neuron neuron = layer.getNeurons().get((int) Math.round(Math.random() * (layer.getNeurons().size() - 1))); | |
neuron.mutate(); | |
} | |
} | |
public List<Layer> getLayers() { | |
return layers; | |
} | |
public NeuralNetwork clone() { | |
List<Layer> cloned = new ArrayList<>(); | |
layers.stream().forEach(l -> cloned.add(l.clone())); | |
NeuralNetwork network = new NeuralNetwork(layerStructure); | |
network.layers = cloned; | |
return network; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package redempt.numberrecognition.ai; | |
import java.util.Random; | |
public class Neuron { | |
protected double[] weights = null; | |
protected double bias = 0; | |
protected double value = 0; | |
private Neuron(double[] weights, double bias) { | |
this.weights = weights; | |
this.bias = bias; | |
} | |
public Neuron() { | |
bias = Math.random() - 0.5; | |
} | |
public void generateWeights(int size) { | |
weights = new double[size]; | |
for (int i = 0; i < weights.length; i++) { | |
weights[i] = (Math.random() * 3) - 1.5; | |
} | |
} | |
public void reset() { | |
value = 0; | |
} | |
public void feed(double value) { | |
this.value += value; | |
} | |
public double[] output() { | |
// if (weights == null) { | |
// return new double[] {process(value + bias)}; | |
// } | |
double[] outputs = new double[weights.length]; | |
for (int i = 0; i < outputs.length; i++) { | |
outputs[i] = process((weights[i] * value) + bias); | |
} | |
return outputs; | |
} | |
public void mutate() { | |
if (Math.random() > 0.9) { | |
bias = Math.random() - 0.5; | |
} else { | |
Random random = new Random(); | |
weights[random.nextInt(weights.length - 1)] = (Math.random() * 3) - 1.5; | |
} | |
} | |
public Neuron clone() { | |
return new Neuron(weights.clone(), bias); | |
} | |
protected static double process(double num) { | |
// return 1d / (1d + Math.exp(-num)); | |
// return num; | |
return Math.tanh(num); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package redempt.numberrecognition.ai; | |
import java.util.Objects; | |
public class NeuronLocation { | |
private int layer; | |
private int number; | |
private int focus; | |
public NeuronLocation(int layer, int number) { | |
this.layer = layer; | |
this.number = number; | |
} | |
public NeuronLocation(int layer, int number, int focus) { | |
this.layer = layer; | |
this.number = number; | |
this.focus = focus; | |
} | |
public int getFocus() { | |
return focus; | |
} | |
public void setFocus(int focus) { | |
this.focus = focus; | |
} | |
public int getLayer() { | |
return layer; | |
} | |
public int getNumber() { | |
return number; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (o instanceof NeuronLocation) { | |
NeuronLocation other = (NeuronLocation) o; | |
return other.layer == this.layer && other.number == this.number; | |
} | |
return super.equals(o); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(number, layer); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment