Erste Implementation eines einfachen neuronalen Netzes

Vorbild zur Implementation: https://github.com/wheresvic/neuralnet
This commit is contained in:
ngb
2022-07-10 22:33:21 +02:00
parent 54762cb3e6
commit 4ea526b239
7 changed files with 770 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
package schule.ngb.zm.ml;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MLMathTest {
@Test
void matrixMultiply() {
double[][] A = new double[][]{
{1.0, 2.0, 3.0},
{-5.0, -4.0, -3.0},
{0.0, -10.0, 10.0}
};
double[][] B = new double[][]{
{0.0, 1.0},
{2.0, -2.0},
{5.0, -10.0}
};
double[][] result = MLMath.matrixMultiply(A, B);
assertNotNull(result);
assertEquals(A.length, result.length);
assertEquals(B[0].length, result[0].length);
assertArrayEquals(new double[]{19.0, -33.0}, result[0]);
assertArrayEquals(new double[]{-23.0, 33.0}, result[1]);
assertArrayEquals(new double[]{30.0, -80.0}, result[2]);
assertThrowsExactly(IllegalArgumentException.class, () -> MLMath.matrixMultiply(B, A));
}
@Test
void matrixScale() {
double[][] matrix = new double[][]{
{1.0, 2.0, 3.0},
{-5.0, -4.0, -3.0},
{0.0, -10.0, 10.0}
};
double[][] scalars = new double[][]{
{0.0, 1.0, -1.0},
{2.0, -2.0, 10.0},
{5.0, -10.0, 10.0}
};
double[][] result = MLMath.matrixScale(matrix, scalars);
assertNotNull(result);
assertNotSame(matrix, result);
assertArrayEquals(new double[]{0.0, 2.0, -3.0}, result[0]);
assertArrayEquals(new double[]{-10.0, 8.0, -30.0}, result[1]);
assertArrayEquals(new double[]{0.0, 100.0, 100.0}, result[2]);
}
@Test
void matrixApply() {
double[][] matrix = new double[][]{
{1.0, 2.0, 3.0},
{-5.0, -4.0, -3.0},
{0.0, -10.0, 10.0}
};
double[][] result = MLMath.matrixApply(matrix, (d) -> -1*d);
assertNotNull(result);
assertNotSame(matrix, result);
assertArrayEquals(new double[]{-1.0, -2.0, -3.0}, result[0]);
assertArrayEquals(new double[]{5.0, 4.0, 3.0}, result[1]);
assertArrayEquals(new double[]{-0.0, 10.0, -10.0}, result[2]);
}
@Test
void matrixSubtract() {
}
@Test
void matrixAdd() {
}
@Test
void matrixTranspose() {
double[][] matrix = new double[][]{
{1.0, 2.0, 3.0, 4.5},
{-5.0, -4.0, -3.0, 2.1},
{0.0, -10.0, 10.0, 0.9}
};
double[][] result = MLMath.matrixTranspose(matrix);
assertNotNull(result);
assertEquals(4, result.length);
assertEquals(3, result[0].length);
assertArrayEquals(new double[]{1.0, -5.0, 0.0}, result[0]);
assertArrayEquals(new double[]{2.0, -4.0, -10.0}, result[1]);
assertArrayEquals(new double[]{3.0, -3.0, 10.0}, result[2]);
assertArrayEquals(new double[]{4.5, 2.1, 0.9}, result[3]);
}
@Test
void normalize() {
}
}

View File

@@ -0,0 +1,57 @@
package schule.ngb.zm.ml;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
class MatrixTest {
@Test
void initializeIdentity() {
Matrix m = new Matrix(4, 4);
m.initializeIdentity();
assertArrayEquals(new double[]{1.0, 0.0, 0.0, 0.0}, m.coefficients[0]);
assertArrayEquals(new double[]{0.0, 1.0, 0.0, 0.0}, m.coefficients[1]);
assertArrayEquals(new double[]{0.0, 0.0, 1.0, 0.0}, m.coefficients[2]);
assertArrayEquals(new double[]{0.0, 0.0, 0.0, 1.0}, m.coefficients[3]);
}
@Test
void initializeOne() {
Matrix m = new Matrix(4, 4);
m.initializeOne();
double[] ones = new double[]{1.0, 1.0, 1.0, 1.0};
assertArrayEquals(ones, m.coefficients[0]);
assertArrayEquals(ones, m.coefficients[1]);
assertArrayEquals(ones, m.coefficients[2]);
assertArrayEquals(ones, m.coefficients[3]);
}
@Test
void initializeZero() {
Matrix m = new Matrix(4, 4);
m.initializeZero();
double[] zeros = new double[]{0.0, 0.0, 0.0, 0.0};
assertArrayEquals(zeros, m.coefficients[0]);
assertArrayEquals(zeros, m.coefficients[1]);
assertArrayEquals(zeros, m.coefficients[2]);
assertArrayEquals(zeros, m.coefficients[3]);
}
@Test
void initializeRandom() {
Matrix m = new Matrix(4, 4);
m.initializeRandom(-1, 1);
assertTrue(Arrays.stream(m.coefficients[0]).allMatch((d) -> -1.0 <= d && d < 1.0));
assertTrue(Arrays.stream(m.coefficients[1]).allMatch((d) -> -1.0 <= d && d < 1.0));
assertTrue(Arrays.stream(m.coefficients[2]).allMatch((d) -> -1.0 <= d && d < 1.0));
assertTrue(Arrays.stream(m.coefficients[3]).allMatch((d) -> -1.0 <= d && d < 1.0));
}
}

View File

@@ -0,0 +1,171 @@
package schule.ngb.zm.ml;
import org.junit.jupiter.api.Test;
import schule.ngb.zm.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
class NeuralNetworkTest {
@Test
void learnCalc() {
Log.enableGlobalDebugging();
int INPUT_SIZE = 50;
int PREDICT_SIZE = 4;
int TRAINING_CYCLES = 40000;
CalcType OPERATION = CalcType.SUB;
// Create neural network with layer1: 4 neurones, layer2: 1 neuron
NeuralNetwork net = new NeuralNetwork(2, 8, 4, 1);
List<TestData> trainingData = createTrainingSet(INPUT_SIZE, OPERATION);
double[][] inputs = new double[INPUT_SIZE][2];
double[][] outputs = new double[INPUT_SIZE][1];
for( int i = 0; i < trainingData.size(); i++ ) {
inputs[i][0] = trainingData.get(i).a;
inputs[i][1] = trainingData.get(i).b;
outputs[i][0] = trainingData.get(i).result;
}
System.out.println("Training the neural net to learn "+OPERATION+"...");
net.train(inputs, outputs, TRAINING_CYCLES);
System.out.println(" finished training");
for( int i = 1; i <= net.getLayerCount(); i++ ) {
System.out.println("Layer " +i + " weights");
System.out.println(net.getLayer(i).weights);
}
// calculate the predictions on unknown data
List<TestData> predictionSet = createTrainingSet(PREDICT_SIZE, OPERATION);
for( TestData t : predictionSet ) {
predict(t, net);
}
}
public static void predict( TestData data, NeuralNetwork net ) {
double[][] testInput = new double[][]{{data.a, data.b}};
net.predict(testInput);
// then
System.out.printf(
"Prediction on data (%.2f, %.2f) was %.4f, expected %.2f (of by %.4f)\n",
data.a, data.b,
net.getOutput()[0][0],
data.result,
net.getOutput()[0][0] - data.result
);
}
private List<TestData> createTrainingSet( int trainingSetSize, CalcType operation ) {
Random random = new Random();
List<TestData> tuples = new ArrayList<>();
for( int i = 0; i < trainingSetSize; i++ ) {
double s1 = random.nextDouble() * 0.5;
double s2 = random.nextDouble() * 0.5;
switch( operation ) {
case ADD:
tuples.add(new AddData(s1, s2));
break;
case SUB:
tuples.add(new SubData(s1, s2));
break;
case MUL:
tuples.add(new MulData(s1, s2));
break;
case DIV:
tuples.add(new DivData(s1, s2));
break;
case MOD:
tuples.add(new ModData(s1, s2));
break;
}
}
return tuples;
}
private static enum CalcType {
ADD, SUB, MUL, DIV, MOD
}
private static abstract class TestData {
double a;
double b;
double result;
CalcType type;
TestData( double a, double b ) {
this.a = a;
this.b = b;
}
}
private static final class AddData extends TestData {
CalcType type = CalcType.ADD;
public AddData( double a, double b ) {
super(a, b);
result = a + b;
}
}
private static final class SubData extends TestData {
CalcType type = CalcType.SUB;
public SubData( double a, double b ) {
super(a, b);
result = a - b;
}
}
private static final class MulData extends TestData {
CalcType type = CalcType.MUL;
public MulData( double a, double b ) {
super(a, b);
result = a * b;
}
}
private static final class DivData extends TestData {
CalcType type = CalcType.DIV;
public DivData( double a, double b ) {
super(a, b);
if( b == 0.0 ) {
b = .1;
}
result = a / b;
}
}
private static final class ModData extends TestData {
CalcType type = CalcType.MOD;
public ModData( double b, double a ) {
super(b, a);
result = a % b;
}
}
}