java doc und refactorings

This commit is contained in:
ngb
2022-07-20 17:09:09 +02:00
parent d3997561fc
commit 16477463d4
6 changed files with 510 additions and 108 deletions

View File

@@ -4,12 +4,60 @@ import schule.ngb.zm.Constants;
import java.util.function.DoubleUnaryOperator; import java.util.function.DoubleUnaryOperator;
// TODO: Move Math into Matrix class /**
// TODO: Implement support for optional sci libs * Eine einfache Implementierung der {@link MLMatrix} zur Verwendung in
* {@link NeuralNetwork}s.
* <p>
* Diese Klasse stellt die interne Implementierung der Matrixoperationen dar,
* die zur Berechnung der Gewichte in einem {@link NeuronLayer} notwendig sind.
* <p>
* Die Klasse ist nur minimal optimiert und sollte nur für kleine Netze
* verwendet werden. Für größere Netze sollte auf eine der optionalen
* Bibliotheken wie
* <a href="">Colt</a> zurückgegriffen werden.
*/
public final class DoubleMatrix implements MLMatrix { public final class DoubleMatrix implements MLMatrix {
private int columns, rows; /**
* Anzahl Zeilen der Matrix.
*/
private int rows;
/**
* Anzahl Spalten der Matrix.
*/
private int columns;
/**
* Die Koeffizienten der Matrix.
* <p>
* Um den Overhead bei Speicher und Zugriffszeiten von zweidimensionalen
* Arrays zu vermeiden wird ein eindimensionales Array verwendet und die
* Indizes mit Spaltenpriorität berechnet. Der Index i des Koeffizienten
* {@code r,c} in Zeile {@code r} und Spalte {@code c} wird bestimmt durch
* <pre>
* i = c * rows + r
* </pre>
* <p>
* Die Werte einer Spalte liegen also hintereinander im Array. Dies sollte
* einen leichten Vorteil bei der {@link #colSums() Spaltensummen} geben.
* Generell sollte eine Iteration über die Matrix der Form
* <pre><code>
* for( int j = 0; j < columns; j++ ) {
* for( int i = 0; i < rows; i++ ) {
* // ...
* }
* }
* </code></pre>
* etwas schneller sein als
* <pre><code>
* for( int i = 0; i < rows; i++ ) {
* for( int j = 0; j < columns; j++ ) {
* // ...
* }
* }
* </code></pre>
*/
double[] coefficients; double[] coefficients;
public DoubleMatrix( int rows, int cols ) { public DoubleMatrix( int rows, int cols ) {
@@ -29,6 +77,11 @@ public final class DoubleMatrix implements MLMatrix {
} }
} }
/**
* Initialisiert diese Matrix als Kopie der angegebenen Matrix.
*
* @param other Die zu kopierende Matrix.
*/
public DoubleMatrix( DoubleMatrix other ) { public DoubleMatrix( DoubleMatrix other ) {
this.rows = other.rows(); this.rows = other.rows();
this.columns = other.columns(); this.columns = other.columns();
@@ -39,55 +92,100 @@ public final class DoubleMatrix implements MLMatrix {
rows * columns); rows * columns);
} }
/**
* {@inheritDoc}
*/
@Override
public int columns() { public int columns() {
return columns; return columns;
} }
/**
* {@inheritDoc}
*/
@Override
public int rows() { public int rows() {
return rows; return rows;
} }
public double[][] coefficients() { /**
return new double[rows][columns]; * {@inheritDoc}
} */
int idx( int r, int c ) { int idx( int r, int c ) {
return c * rows + r; return c * rows + r;
} }
/**
* {@inheritDoc}
*/
@Override
public double get( int row, int col ) { public double get( int row, int col ) {
return coefficients[idx(row, col)]; try {
return coefficients[idx(row, col)];
} catch( ArrayIndexOutOfBoundsException ex ) {
throw new IllegalArgumentException("No element at row=" + row + ", column=" + col, ex);
}
} }
/**
* {@inheritDoc}
*/
@Override
public MLMatrix set( int row, int col, double value ) { public MLMatrix set( int row, int col, double value ) {
coefficients[idx(row, col)] = value; try {
coefficients[idx(row, col)] = value;
} catch( ArrayIndexOutOfBoundsException ex ) {
throw new IllegalArgumentException("No element at row=" + row + ", column=" + col, ex);
}
return this; return this;
} }
/**
* {@inheritDoc}
*/
@Override
public MLMatrix initializeRandom() { public MLMatrix initializeRandom() {
return initializeRandom(-1.0, 1.0); return initializeRandom(-1.0, 1.0);
} }
/**
* {@inheritDoc}
*/
@Override
public MLMatrix initializeRandom( double lower, double upper ) { public MLMatrix initializeRandom( double lower, double upper ) {
applyInPlace(( d ) -> ((upper - lower) * Constants.random()) + lower); applyInPlace(( d ) -> ((upper - lower) * Constants.random()) + lower);
return this; return this;
} }
/**
* {@inheritDoc}
*/
@Override
public MLMatrix initializeOne() { public MLMatrix initializeOne() {
applyInPlace(( d ) -> 1.0); applyInPlace(( d ) -> 1.0);
return this; return this;
} }
/**
* {@inheritDoc}
*/
@Override
public MLMatrix initializeZero() { public MLMatrix initializeZero() {
applyInPlace(( d ) -> 0.0); applyInPlace(( d ) -> 0.0);
return this; return this;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix duplicate() { public MLMatrix duplicate() {
return new DoubleMatrix(this); return new DoubleMatrix(this);
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix multiplyTransposed( MLMatrix B ) { public MLMatrix multiplyTransposed( MLMatrix B ) {
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj( /*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
@@ -109,6 +207,9 @@ public final class DoubleMatrix implements MLMatrix {
return result; return result;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix multiplyAddBias( final MLMatrix B, final MLMatrix C ) { public MLMatrix multiplyAddBias( final MLMatrix B, final MLMatrix C ) {
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj( /*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
@@ -131,6 +232,9 @@ public final class DoubleMatrix implements MLMatrix {
return result; return result;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix transposedMultiplyAndScale( final MLMatrix B, final double scalar ) { public MLMatrix transposedMultiplyAndScale( final MLMatrix B, final double scalar ) {
/*return new DoubleMatrix(IntStream.range(0, columns).parallel().mapToObj( /*return new DoubleMatrix(IntStream.range(0, columns).parallel().mapToObj(
@@ -153,6 +257,9 @@ public final class DoubleMatrix implements MLMatrix {
return result; return result;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix add( MLMatrix B ) { public MLMatrix add( MLMatrix B ) {
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj( /*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
@@ -169,6 +276,9 @@ public final class DoubleMatrix implements MLMatrix {
return sum; return sum;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix addInPlace( MLMatrix B ) { public MLMatrix addInPlace( MLMatrix B ) {
for( int j = 0; j < columns; j++ ) { for( int j = 0; j < columns; j++ ) {
@@ -179,6 +289,9 @@ public final class DoubleMatrix implements MLMatrix {
return this; return this;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix sub( MLMatrix B ) { public MLMatrix sub( MLMatrix B ) {
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj( /*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
@@ -195,6 +308,9 @@ public final class DoubleMatrix implements MLMatrix {
return diff; return diff;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix colSums() { public MLMatrix colSums() {
/*DoubleMatrix colSums = new DoubleMatrix(1, columns); /*DoubleMatrix colSums = new DoubleMatrix(1, columns);
@@ -214,6 +330,9 @@ public final class DoubleMatrix implements MLMatrix {
return colSums; return colSums;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix scaleInPlace( final double scalar ) { public MLMatrix scaleInPlace( final double scalar ) {
for( int i = 0; i < coefficients.length; i++ ) { for( int i = 0; i < coefficients.length; i++ ) {
@@ -222,6 +341,9 @@ public final class DoubleMatrix implements MLMatrix {
return this; return this;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix scaleInPlace( final MLMatrix S ) { public MLMatrix scaleInPlace( final MLMatrix S ) {
for( int j = 0; j < columns; j++ ) { for( int j = 0; j < columns; j++ ) {
@@ -232,6 +354,9 @@ public final class DoubleMatrix implements MLMatrix {
return this; return this;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix apply( DoubleUnaryOperator op ) { public MLMatrix apply( DoubleUnaryOperator op ) {
DoubleMatrix result = new DoubleMatrix(rows, columns); DoubleMatrix result = new DoubleMatrix(rows, columns);
@@ -241,6 +366,9 @@ public final class DoubleMatrix implements MLMatrix {
return result; return result;
} }
/**
* {@inheritDoc}
*/
@Override @Override
public MLMatrix applyInPlace( DoubleUnaryOperator op ) { public MLMatrix applyInPlace( DoubleUnaryOperator op ) {
for( int i = 0; i < coefficients.length; i++ ) { for( int i = 0; i < coefficients.length; i++ ) {

View File

@@ -2,142 +2,312 @@ package schule.ngb.zm.ml;
import java.util.function.DoubleUnaryOperator; import java.util.function.DoubleUnaryOperator;
/**
* Interface für Matrizen, die in {@link NeuralNetwork} Klassen verwendet
* werden.
* <p>
* Eine implementierende Klasse muss generell zwei Konstruktoren bereitstellen:
* <ol>
* <li> {@code MLMatrix(int rows, int columns)} erstellt eine Matrix mit den
* angegebenen Dimensionen und setzt alle Koeffizienten auf 0.
* <li> {@code MLMatrix(double[][] coefficients} erstellt eine Matrix mit der
* durch das Array gegebenen Dimensionen und setzt die Werte auf die
* jeweiligen Werte des Arrays.
* </ol>
* <p>
* Das Interface ist nicht dazu gedacht eine allgemeine Umsetzung für
* Matrizen-Algebra abzubilden, sondern soll gezielt die im Neuralen Netzwerk
* verwendeten Algorithmen umsetzen. Einerseits würde eine ganz allgemeine
* Matrizen-Klasse nicht im Rahmen der Zeichenmaschine liegen und auf der
* anderen Seite bietet eine Konzentration auf die verwendeten Algorithmen mehr
* Spielraum zur Optimierung.
* <p>
* Intern wird das Interface von {@link DoubleMatrix} implementiert. Die Klasse
* ist eine weitestgehend naive Implementierung der Algorithmen mit kleineren
* Optimierungen. Die Verwendung eines generalisierten Interfaces erlaubt aber
* zukünftig die optionale Integration spezialisierterer Algebra-Bibliotheken
* wie
* <a href="https://dst.lbl.gov/ACSSoftware/colt/">Colt</a>, um auch große
* Netze effizient berechnen zu können.
*/
public interface MLMatrix { public interface MLMatrix {
/**
* Die Anzahl der Spalten der Matrix.
*
* @return Spaltenzahl.
*/
int columns(); int columns();
/**
* Die Anzahl der Zeilen der Matrix.
*
* @return Zeilenzahl.
*/
int rows(); int rows();
double[][] coefficients(); /**
* Gibt den Wert an der angegebenen Stelle der Matrix zurück.
*
* @param row Die Spaltennummer zwischen 0 und {@code rows()-1}.
* @param col Die Zeilennummer zwischen 0 und {@code columns()-1}
* @return Den Koeffizienten in der Zeile {@code row} und der Spalte
* {@code col}.
* @throws IllegalArgumentException Falls {@code row >= rows()} oder
* {@code col >= columns()}.
*/
double get( int row, int col ) throws IllegalArgumentException;
double get( int row, int col ); /**
* Setzt den Wert an der angegebenen Stelle der Matrix.
MLMatrix set( int row, int col, double value ); *
* @param row Die Spaltennummer zwischen 0 und {@code rows()-1}.
* @param col Die Zeilennummer zwischen 0 und {@code columns()-1}
* @param value Der neue Wert.
* @return Diese Matrix selbst (method chaining).
* @throws IllegalArgumentException Falls {@code row >= rows()} oder
* {@code col >= columns()}.
*/
MLMatrix set( int row, int col, double value ) throws IllegalArgumentException;
/**
* Setzt jeden Wert in der Matrix auf eine Zufallszahl zwischen -1 und 1.
* <p>
* Nach Möglichkeit sollte der
* {@link schule.ngb.zm.Constants#random(int, int) Zufallsgenerator der
* Zeichenmaschine} verwendet werden.
*
* @return Diese Matrix selbst (method chaining).
*/
MLMatrix initializeRandom(); MLMatrix initializeRandom();
/**
* Setzt jeden Wert in der Matrix auf eine Zufallszahl innerhalb der
* angegebenen Grenzen.
* <p>
* Nach Möglichkeit sollte der
* {@link schule.ngb.zm.Constants#random(int, int) Zufallsgenerator der
* Zeichenmaschine} verwendet werden.
*
* @param lower Untere Grenze der Zufallszahlen.
* @param upper Obere Grenze der Zufallszahlen.
* @return Diese Matrix selbst (method chaining).
*/
MLMatrix initializeRandom( double lower, double upper ); MLMatrix initializeRandom( double lower, double upper );
/**
* Setzt alle Werte der Matrix auf 1.
*
* @return Diese Matrix selbst (method chaining).
*/
MLMatrix initializeOne(); MLMatrix initializeOne();
/**
* Setzt alle Werte der Matrix auf 0.
*
* @return Diese Matrix selbst (method chaining).
*/
MLMatrix initializeZero(); MLMatrix initializeZero();
//MLMatrix transpose(); /**
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der Matrixoperation
//MLMatrix multiply( MLMatrix B ); * <pre>
* C = this . B + V'
* </pre>
* wobei {@code this} dieses Matrixobjekt ist und {@code .} für die
* Matrixmultiplikation steht. {@vode V'} ist die Matrix {@code V}
* {@code rows()}-mal untereinander wiederholt.
* <p>
* Wenn diese Matrix die Dimension r x c hat, dann muss die Matrix {@code B}
* die Dimension c x m haben und {@code V} eine 1 x m Matrix sein. Die
* Matrix {@code V'} hat also die Dimension r x m, ebenso wie das Ergebnis
* der Operation.
*
* @param B Eine {@code columns()} x m Matrix mit der Multipliziert wird.
* @param V Eine 1 x {@code B.columns()} Matrix mit den Bias-Werten.
* @return Eine {@code rows()} x m Matrix.
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
* zur Operation passen. Also
* {@code this.columns() != B.rows()} oder
* {@code B.columns() != V.columns()} oder
* {@code V.rows() != 1}.
*/
MLMatrix multiplyAddBias( MLMatrix B, MLMatrix V ) throws IllegalArgumentException;
/** /**
* Erzeugt eine neue Matrix <em>C</em> mit dem Ergebnis der Matrixoperation * Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der Matrixoperation
* <pre> * <pre>
* C = A.B + V * C = this . t(B)
* </pre> * </pre>
* wobei <em>A</em> dieses Matrixobjekt ist und {@code .} für die * wobei {@code this} dieses Matrixobjekt ist, {@code t(B)} die
* Transposition der Matrix {@code B} ist und {@code .} für die
* Matrixmultiplikation steht. * Matrixmultiplikation steht.
* <p>
* Wenn diese Matrix die Dimension r x c hat, dann muss die Matrix {@code B}
* die Dimension m x c haben und das Ergebnis ist eine r x m Matrix.
* *
* @param B * @param B Eine m x {@code columns()} Matrix.
* @param V * @return Eine {@code rows()} x m Matrix.
* @return * @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
* zur Operation passen. Also
* {@code this.columns() != B.columns()}.
*/ */
MLMatrix multiplyAddBias( MLMatrix B, MLMatrix V ); MLMatrix multiplyTransposed( MLMatrix B ) throws IllegalArgumentException;
/** /**
* Erzeugt eine neue Matrix <em>C</em> mit dem Ergebnis der Matrixoperation * Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der Matrixoperation
* <pre> * <pre>
* C = A.t(B) * C = t(this) . B * scalar
* </pre> * </pre>
* wobei <em>A</em> dieses Matrixobjekt ist und {@code t(B)} für die * wobei {@code this} dieses Matrixobjekt ist, {@code t(this)} die
* Transposition der Matrix <em>B</em>> steht. * Transposition dieser Matrix ist und {@code .} für die
* Matrixmultiplikation steht. {@code *} bezeichnet die
* Skalarmultiplikation, bei der jeder Wert der Matrix mit {@code scalar}
* multipliziert wird.
* <p>
* Wenn diese Matrix die Dimension r x c hat, dann muss die Matrix {@code B}
* die Dimension r x m haben und das Ergebnis ist eine c x m Matrix.
* *
* @param B * @param B Eine m x {@code columns()} Matrix.
* @return * @return Eine {@code rows()} x m Matrix.
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
* zur Operation passen. Also
* {@code this.rows() != B.rows()}.
*/ */
MLMatrix multiplyTransposed( MLMatrix B ); MLMatrix transposedMultiplyAndScale( MLMatrix B, double scalar ) throws IllegalArgumentException;
MLMatrix transposedMultiplyAndScale( MLMatrix B, double scalar );
/** /**
* Erzeugt eine neue Matrix <em>C</em> mit dem Ergebnis der * Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der komponentenweisen
* komponentenweisen Matrix-Addition * Matrix-Addition
* <pre> * <pre>
* C = A+B * C = this + B
* </pre> * </pre>
* wobei <em>A</em> dieses Matrixobjekt ist. Für ein Element * wobei {@code this} dieses Matrixobjekt ist. Für ein Element {@code C_ij}
* <em>C_ij</em> in <em>C</em> gilt * in {@code C} gilt
* <pre> * <pre>
* C_ij = A_ij + B_ij * C_ij = A_ij + B_ij
* </pre> * </pre>
* <p>
* Die Matrix {@code B} muss dieselbe Dimension wie diese Matrix haben.
* *
* @param B Die zweite Matrix. * @param B Eine {@code rows()} x {@code columns()} Matrix.
* @return Ein neues Matrixobjekt mit dem Ergebnis. * @return Eine {@code rows()} x {@code columns()} Matrix.
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
* zur Operation passen. Also
* {@code this.rows() != B.rows()} oder
* {@code this.columns() != B.columns()}.
*/ */
MLMatrix add( MLMatrix B ); MLMatrix add( MLMatrix B ) throws IllegalArgumentException;
/** /**
* Setzt dies Matrix auf das Ergebnis der * Setzt diese Matrix auf das Ergebnis der komponentenweisen
* komponentenweisen Matrix-Addition * Matrix-Addition
* <pre> * <pre>
* A = A+B * A' = A + B
* </pre> * </pre>
* wobei <em>A</em> dieses Matrixobjekt ist. Für ein Element * wobei {@code A} dieses Matrixobjekt ist und {@code A'} diese Matrix nach
* <em>A_ij</em> in <em>A</em> gilt * der Operation. Für ein Element {@code A'_ij} in {@code A'} gilt
* <pre> * <pre>
* A_ij = A_ij + B_ij * A'_ij = A_ij + B_ij
* </pre> * </pre>
* <p>
* Die Matrix {@code B} muss dieselbe Dimension wie diese Matrix haben.
* *
* @param B Die zweite Matrix. * @param B Eine {@code rows()} x {@code columns()} Matrix.
* @return Diese Matrix selbst (method chaining). * @return Eine {@code rows()} x {@code columns()} Matrix.
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
* zur Operation passen. Also
* {@code this.rows() != B.rows()} oder
* {@code this.columns() != B.columns()}.
*/ */
MLMatrix addInPlace( MLMatrix B ); MLMatrix addInPlace( MLMatrix B ) throws IllegalArgumentException;
/** /**
* Erzeugt eine neue Matrix <em>C</em> mit dem Ergebnis der * Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der komponentenweisen
* komponentenweisen Matrix-Subtraktion * Matrix-Subtraktion
* <pre> * <pre>
* C = A-B * C = A - B
* </pre> * </pre>
* wobei <em>A</em> dieses Matrixobjekt ist. Für ein Element * wobei {@code A} dieses Matrixobjekt ist. Für ein Element {@code C_ij} in
* <em>C_ij</em> in <em>C</em> gilt * {@code C} gilt
* <pre> * <pre>
* C_ij = A_ij - B_ij * C_ij = A_ij - B_ij
* </pre> * </pre>
* <p>
* Die Matrix {@code B} muss dieselbe Dimension wie diese Matrix haben.
* *
* @param B * @param B Eine {@code rows()} x {@code columns()} Matrix.
* @return * @return Eine {@code rows()} x {@code columns()} Matrix.
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
* zur Operation passen. Also
* {@code this.rows() != B.rows()} oder
* {@code this.columns() != B.columns()}.
*/ */
MLMatrix sub( MLMatrix B ); MLMatrix sub( MLMatrix B ) throws IllegalArgumentException;
/**
* Multipliziert jeden Wert dieser Matrix mit dem angegebenen Skalar.
* <p>
* Ist {@code A} dieses Matrixobjekt und {@code A'} diese Matrix nach der
* Operation, dann gilt für ein Element {@code A'_ij} in {@code A'}
* <pre>
* A'_ij = A_ij * scalar
* </pre>
*
* @param scalar Ein Skalar.
* @return Diese Matrix selbst (method chaining)
*/
MLMatrix scaleInPlace( double scalar ); MLMatrix scaleInPlace( double scalar );
MLMatrix scaleInPlace( MLMatrix S ); /**
* Multipliziert jeden Wert dieser Matrix mit dem entsprechenden Wert in der
* Matrix {@code S}.
* <p>
* Ist {@code A} dieses Matrixobjekt und {@code A'} diese Matrix nach der
* Operation, dann gilt für ein Element {@code A'_ij} in {@code A'}
* <pre>
* A'_ij = A_ij * S_ij
* </pre>
*
* @param S Eine {@code rows()} x {@code columns()} Matrix.
* @return Diese Matrix selbst (method chaining)
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
* zur Operation passen. Also
* {@code this.rows() != B.rows()} oder
* {@code this.columns() != B.columns()}.
*/
MLMatrix scaleInPlace( MLMatrix S ) throws IllegalArgumentException;
/** /**
* Berechnet eine neue Matrix mit nur einer Zeile, die die Spaltensummen * Berechnet eine neue Matrix mit nur einer Zeile, die die Spaltensummen
* dieser Matrix enthalten. * dieser Matrix enthalten.
* @return *
* @return Eine 1 x {@code columns()} Matrix.
*/ */
MLMatrix colSums(); MLMatrix colSums();
/** /**
* Endet die gegebene Funktion auf jeden Wert der Matrix an. * Erzeugt eine neue Matrix, deren Werte gleich den Werten dieser Matrix
* nach der Anwendung der angegebenen Funktion sind.
* *
* @param op * @param op Eine Operation {@code (double) -> double}.
* @return * @return Eine {@code rows()} x {@code columns()} Matrix.
*/ */
MLMatrix apply( DoubleUnaryOperator op ); MLMatrix apply( DoubleUnaryOperator op );
/** /**
* Endet die gegebene Funktion auf jeden Wert der Matrix an. * Endet die gegebene Funktion auf jeden Wert der Matrix an.
* *
* @param op * @param op Eine Operation {@code (double) -> double}.
* @return * @return Diese Matrix selbst (method chaining)
*/ */
MLMatrix applyInPlace( DoubleUnaryOperator op ); MLMatrix applyInPlace( DoubleUnaryOperator op );
/** /**
* Erzeugt eine neue Matrix mit denselben Dimenstionen und Koeffizienten wie * Erzeugt eine neue Matrix mit denselben Dimensionen und Koeffizienten wie
* diese Matrix. * diese Matrix.
* *
* @return * @return Eine Kopie dieser Matrix.
*/ */
MLMatrix duplicate(); MLMatrix duplicate();

View File

@@ -6,14 +6,27 @@ import schule.ngb.zm.util.Log;
import java.util.function.DoubleUnaryOperator; import java.util.function.DoubleUnaryOperator;
/**
* Zentrale Klasse zur Erstellung neuer Matrizen. Generell sollten neue Matrizen
* nicht direkt erstellt werden, sondern durch den Aufruf von
* {@link #create(int, int)} oder {@link #create(double[][])}. Die Fabrik
* ermittelt automatisch die beste verfügbare Implementierung und initialisiert
* eine entsprechende Implementierung von {@link MLMatrix}.
* <p>
* Derzeit werden die optionale Bibliothek <a
* href="https://dst.lbl.gov/ACSSoftware/colt/">Colt</a> und die interne
* Implementierung {@link DoubleMatrix} unterstützt.
*/
public class MatrixFactory { public class MatrixFactory {
public static void main( String[] args ) { /**
System.out.println( * Erstellt eine neue Matrix mit den angegebenen Dimensionen und
MatrixFactory.create(new double[][]{{1.0, 0.0}, {0.0, 1.0}}).toString() * initialisiert alle Werte mit 0.
); *
} * @param rows Anzahl der Zeilen.
* @param cols Anzahl der Spalten.
* @return Eine {@code rows} x {@code cols} Matrix.
*/
public static final MLMatrix create( int rows, int cols ) { public static final MLMatrix create( int rows, int cols ) {
try { try {
return getMatrixType().getDeclaredConstructor(int.class, int.class).newInstance(rows, cols); return getMatrixType().getDeclaredConstructor(int.class, int.class).newInstance(rows, cols);
@@ -23,6 +36,14 @@ public class MatrixFactory {
return new DoubleMatrix(rows, cols); return new DoubleMatrix(rows, cols);
} }
/**
* Erstellt eine neue Matrix mit den Dimensionen des angegebenen Arrays und
* initialisiert die Werte mit den entsprechenden Werten des Arrays.
*
* @param values Die Werte der Matrix.
* @return Eine {@code values.length} x {@code values[0].length} Matrix mit
* den Werten des Arrays.
*/
public static final MLMatrix create( double[][] values ) { public static final MLMatrix create( double[][] values ) {
try { try {
return getMatrixType().getDeclaredConstructor(double[][].class).newInstance((Object) values); return getMatrixType().getDeclaredConstructor(double[][].class).newInstance((Object) values);
@@ -32,8 +53,17 @@ public class MatrixFactory {
return new DoubleMatrix(values); return new DoubleMatrix(values);
} }
/**
* Die verwendete {@link MLMatrix} Implementierung, aus der Matrizen erzeugt
* werden.
*/
static Class<? extends MLMatrix> matrixType = null; static Class<? extends MLMatrix> matrixType = null;
/**
* Ermittelt die beste verfügbare Implementierung von {@link MLMatrix}.
*
* @return Die verwendete {@link MLMatrix} Implementierung.
*/
private static final Class<? extends MLMatrix> getMatrixType() { private static final Class<? extends MLMatrix> getMatrixType() {
if( matrixType == null ) { if( matrixType == null ) {
try { try {
@@ -50,6 +80,10 @@ public class MatrixFactory {
private static final Log LOG = Log.getLogger(MatrixFactory.class); private static final Log LOG = Log.getLogger(MatrixFactory.class);
/**
* Interner Wrapper der DoubleMatrix2D Klasse aus der Colt Bibliothek, um
* das {@link MLMatrix} Interface zu implementieren.
*/
static class ColtMatrix implements MLMatrix { static class ColtMatrix implements MLMatrix {
cern.colt.matrix.DoubleMatrix2D matrix; cern.colt.matrix.DoubleMatrix2D matrix;
@@ -87,11 +121,6 @@ public class MatrixFactory {
return this; return this;
} }
@Override
public double[][] coefficients() {
return this.matrix.toArray();
}
@Override @Override
public MLMatrix initializeRandom() { public MLMatrix initializeRandom() {
return initializeRandom(-1.0, 1.0); return initializeRandom(-1.0, 1.0);

View File

@@ -15,7 +15,7 @@ public class NeuralNetwork {
Writer writer = ResourceStreamProvider.getWriter(source); Writer writer = ResourceStreamProvider.getWriter(source);
PrintWriter out = new PrintWriter(writer) PrintWriter out = new PrintWriter(writer)
) { ) {
for( NeuronLayer layer: network.layers ) { for( NeuronLayer layer : network.layers ) {
out.print(layer.getNeuronCount()); out.print(layer.getNeuronCount());
out.print(' '); out.print(' ');
out.print(layer.getInputCount()); out.print(layer.getInputCount());
@@ -23,20 +23,44 @@ public class NeuralNetwork {
for( int i = 0; i < layer.getInputCount(); i++ ) { for( int i = 0; i < layer.getInputCount(); i++ ) {
for( int j = 0; j < layer.getNeuronCount(); j++ ) { for( int j = 0; j < layer.getNeuronCount(); j++ ) {
//out.print(layer.weights.coefficients[i][j]); out.print(layer.weights.get(i, j));
out.print(' '); out.print(' ');
} }
out.println(); out.println();
} }
for( int j = 0; j < layer.getNeuronCount(); j++ ) { for( int j = 0; j < layer.getNeuronCount(); j++ ) {
//out.print(layer.biases[j]); out.print(layer.biases.get(0, j));
out.print(' '); out.print(' ');
} }
out.println(); out.println();
} }
out.flush(); out.flush();
} catch( IOException ex ) { } catch( IOException ex ) {
LOG.warn(ex, ""); LOG.error(ex, "");
}
}
public static void saveToDataFile( String source, NeuralNetwork network ) {
try(
OutputStream stream = ResourceStreamProvider.getOutputStream(source);
DataOutputStream out = new DataOutputStream(stream)
) {
for( NeuronLayer layer : network.layers ) {
out.writeInt(layer.getNeuronCount());
out.writeInt(layer.getInputCount());
for( int i = 0; i < layer.getInputCount(); i++ ) {
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
out.writeDouble(layer.weights.get(i, j));
}
}
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
out.writeDouble(layer.biases.get(0, j));
}
}
out.flush();
} catch( IOException ex ) {
LOG.error(ex, "");
} }
} }
@@ -56,13 +80,13 @@ public class NeuralNetwork {
for( int i = 0; i < inputs; i++ ) { for( int i = 0; i < inputs; i++ ) {
split = in.readLine().split(" "); split = in.readLine().split(" ");
for( int j = 0; j < neurons; j++ ) { for( int j = 0; j < neurons; j++ ) {
//layer.weights.coefficients[i][j] = Double.parseDouble(split[j]); layer.weights.set(i, j, Double.parseDouble(split[j]));
} }
} }
// Load Biases // Load Biases
split = in.readLine().split(" "); split = in.readLine().split(" ");
for( int j = 0; j < neurons; j++ ) { for( int j = 0; j < neurons; j++ ) {
//layer.biases[j] = Double.parseDouble(split[j]); layer.biases.set(0, j, Double.parseDouble(split[j]));
} }
layers.add(layer); layers.add(layer);
@@ -70,29 +94,30 @@ public class NeuralNetwork {
return new NeuralNetwork(layers); return new NeuralNetwork(layers);
} catch( IOException | NoSuchElementException ex ) { } catch( IOException | NoSuchElementException ex ) {
LOG.warn(ex, "Could not load neural network from source <%s>", source); LOG.error(ex, "Could not load neural network from source <%s>", source);
} }
return null; return null;
} }
/*public static NeuralNetwork loadFromFile( String source ) { public static NeuralNetwork loadFromDataFile( String source ) {
try( try(
InputStream stream = ResourceStreamProvider.getInputStream(source); InputStream stream = ResourceStreamProvider.getInputStream(source);
Scanner in = new Scanner(stream) DataInputStream in = new DataInputStream(stream)
) { ) {
List<NeuronLayer> layers = new LinkedList<>(); List<NeuronLayer> layers = new LinkedList<>();
while( in.hasNext() ) { while( in.available() > 0 ) {
int neurons = in.nextInt(); int neurons = in.readInt();
int inputs = in.nextInt(); int inputs = in.readInt();
NeuronLayer layer = new NeuronLayer(neurons, inputs); NeuronLayer layer = new NeuronLayer(neurons, inputs);
for( int i = 0; i < inputs; i++ ) { for( int i = 0; i < inputs; i++ ) {
for( int j = 0; j < neurons; j++ ) { for( int j = 0; j < neurons; j++ ) {
layer.weights.coefficients[i][j] = in.nextDouble(); layer.weights.set(i, j, in.readDouble());
} }
} }
// Load Biases
for( int j = 0; j < neurons; j++ ) { for( int j = 0; j < neurons; j++ ) {
layer.biases[j] = in.nextDouble(); layer.biases.set(0, j, in.readDouble());
} }
layers.add(layer); layers.add(layer);
@@ -100,10 +125,10 @@ public class NeuralNetwork {
return new NeuralNetwork(layers); return new NeuralNetwork(layers);
} catch( IOException | NoSuchElementException ex ) { } catch( IOException | NoSuchElementException ex ) {
LOG.warn(ex, "Could not load neural network from source <%s>", source); LOG.error(ex, "Could not load neural network from source <%s>", source);
} }
return null; return null;
}*/ }
private NeuronLayer[] layers; private NeuronLayer[] layers;
@@ -128,7 +153,7 @@ public class NeuralNetwork {
for( int i = 0; i < layers.size(); i++ ) { for( int i = 0; i < layers.size(); i++ ) {
this.layers[i] = layers.get(i); this.layers[i] = layers.get(i);
if( i > 0 ) { if( i > 0 ) {
this.layers[i-1].setNextLayer(this.layers[i]); this.layers[i - 1].setNextLayer(this.layers[i]);
} }
} }
} }
@@ -138,7 +163,7 @@ public class NeuralNetwork {
for( int i = 0; i < layers.length; i++ ) { for( int i = 0; i < layers.length; i++ ) {
this.layers[i] = layers[i]; this.layers[i] = layers[i];
if( i > 0 ) { if( i > 0 ) {
this.layers[i-1].setNextLayer(this.layers[i]); this.layers[i - 1].setNextLayer(this.layers[i]);
} }
} }
} }
@@ -146,6 +171,7 @@ public class NeuralNetwork {
public int getLayerCount() { public int getLayerCount() {
return layers.length; return layers.length;
} }
public NeuronLayer[] getLayers() { public NeuronLayer[] getLayers() {
return layers; return layers;
} }

View File

@@ -3,30 +3,45 @@ package schule.ngb.zm.ml;
import java.util.function.DoubleUnaryOperator; import java.util.function.DoubleUnaryOperator;
import java.util.function.Function; import java.util.function.Function;
/**
* Implementierung einer Neuronenebene in einem Neuonalen Netz.
* <p>
* Eine Ebene besteht aus einer Anzahl an <em>Neuronen</em> die jeweils eine
* Anzahl <em>Eingänge</em> haben. Die Eingänge erhalten als Signal die Ausgabe
* der vorherigen Ebene und berechnen die Ausgabe des jeweiligen Neurons.
*/
public class NeuronLayer implements Function<MLMatrix, MLMatrix> { public class NeuronLayer implements Function<MLMatrix, MLMatrix> {
/*public static NeuronLayer fromArray( double[][] weights ) { public static NeuronLayer fromArray( double[][] weights, boolean transpose ) {
NeuronLayer layer = new NeuronLayer(weights[0].length, weights.length); NeuronLayer layer;
if( transpose ) {
layer = new NeuronLayer(weights.length, weights[0].length);
} else {
layer = new NeuronLayer(weights[0].length, weights.length);
}
for( int i = 0; i < weights[0].length; i++ ) { for( int i = 0; i < weights[0].length; i++ ) {
for( int j = 0; j < weights.length; j++ ) { for( int j = 0; j < weights.length; j++ ) {
layer.weights.coefficients[i][j] = weights[i][j]; if( transpose ) {
layer.weights.set(j, i, weights[i][j]);
} else {
layer.weights.set(i, j, weights[i][j]);
}
} }
} }
return layer; return layer;
} }
public static NeuronLayer fromArray( double[][] weights, double[] biases ) { public static NeuronLayer fromArray( double[][] weights, double[] biases, boolean transpose ) {
NeuronLayer layer = new NeuronLayer(weights[0].length, weights.length); NeuronLayer layer = fromArray(weights, transpose);
for( int i = 0; i < weights[0].length; i++ ) {
for( int j = 0; j < weights.length; j++ ) {
layer.weights.coefficients[i][j] = weights[i][j];
}
}
for( int j = 0; j < biases.length; j++ ) { for( int j = 0; j < biases.length; j++ ) {
layer.biases[j] = biases[j]; layer.biases.set(0, j, biases[j]);
} }
return layer; return layer;
}*/ }
MLMatrix weights; MLMatrix weights;
@@ -112,7 +127,7 @@ public class NeuronLayer implements Function<MLMatrix, MLMatrix> {
@Override @Override
public String toString() { public String toString() {
return weights.toString() + "\n" + biases.toString(); return "weights:\n" + weights.toString() + "\nbiases:\n" + biases.toString();
} }
@Override @Override

View File

@@ -1,9 +1,11 @@
package schule.ngb.zm.ml; package schule.ngb.zm.ml;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import schule.ngb.zm.util.Timer;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@@ -389,4 +391,36 @@ class MLMatrixTest {
return String.format("[" + testName + "(" + className + ") " + methodName + "()] " + msg, args); return String.format("[" + testName + "(" + className + ") " + methodName + "()] " + msg, args);
} }
//@ParameterizedTest
//@ValueSource( classes = {MatrixFactory.ColtMatrix.class, DoubleMatrix.class} )
void speed( Class<? extends MLMatrix> mType ) {
MatrixFactory.matrixType = mType;
int N = 10;
int rows = 1000;
int cols = 1000;
Timer timer = new Timer();
MLMatrix M = MatrixFactory.create(rows, cols);
timer.start();
for( int i = 0; i < N; i++ ) {
M.initializeRandom();
}
timer.stop();
System.err.println(msg("%d iterations: %d ms", "initializeRandom", N, timer.getMillis()));
timer.reset();
MLMatrix B = MatrixFactory.create(rows*2, M.columns());
B.initializeRandom();
timer.start();
for( int i = 0; i < N; i++ ) {
M.multiplyTransposed(B);
}
timer.stop();
System.err.println(msg("%d iterations: %d ms", "multiplyTransposed", N, timer.getMillis()));
}
} }