Matrizenrechnung

Du musst dir dazu nur eine Hilfsmethode schreiben, zum Beispiel, um eine nxm-Matrix mit einer mxn-Matrix zu multiplizieren:

Java:
    private List<List<String>> multiply(List<List<String>> a, List<List<String>> b) {
        List<List<String>> c = new ArrayList<>();
        for (int i = 0; i < a.size(); i++) {
            List<String> l1 = a.get(i);
            List<String> l3 = new ArrayList<>();
            for (int j = 0; j < b.get(i).size(); j++) {
                StringBuilder s = new StringBuilder();
                for (int k = 0; k < l1.size(); k++) {
                    List<String> l2 = b.get(k);
                    s.append("(").append(l1.get(k)).append(")*(").append(l2.get(j)).append(")+");
                }
                s = new StringBuilder(StringUtils.left(s.toString(), s.length() - 1));
                l3.add(s.toString());
            }
            c.add(l3);
        }
        return c;
    }

    private void printMat(String c, List<List<String>> m) {
        System.out.println(c + ":");
        for (List<String> list : m) {
            System.out.print("( ");
            for (String string : list) {
                System.out.print(string + " ");
            }
            System.out.println(")");
        }
    }

    private void printAndSolveMat(String name, List<List<String>> m) {
        System.out.println(name + ":");
        for (List<String> list : m) {
            System.out.print("( ");
            for (String string : list) {
                System.out.print(string + " ");
            }
            System.out.println(")");
        }
        System.out.println(name + ":");
        for (List<String> list : m) {
            System.out.print("( ");
            for (String string : list) {
                Node o = Compiler.compile(string);
                System.out.print(o.simplify().beautify() + " ");
            }
            System.out.println(")");
        }
        System.out.println(name + ":");
        for (List<String> list : m) {
            System.out.print("( ");
            for (String string : list) {
                Node o = Compiler.compile(string);
                System.out.print(o.simplify().beautify().val() + " ");
            }
            System.out.println(")");
        }
    }

"Compiler.compile(string).simplify().beautify().val()" ist die Magie.
 
Stimmt, das Was? ist ganz einfach, aber das Wie? und Warum? schon heftiger bei ANNs...

(Leider) ist das Thema AI sehr mit Python besetzt... Java wurde dabei kaum in Erwägung gezogen
 
Ich lasse das wohl lieber mit dem Python-Code in Java umsetzen und fange nochmal von vorne an. Könnt ihr mir ein deutsches -Neuronale Netze Programmieren in Java - Buch empfehlen?
 
Hm. Gemini meint ND4J und Tensorflow Java sind nicht kompatibel .
Wie ich schon sagte, die Vorschläge taugen nix.

Allerdings hatte ich jas vorgeschlagen, bevor ich wusste dass es nicht um allgemeine Mathematik geht, sondern um ANN s...

Für ANNs kann Java verwendet werden, aber das ist dann nicht so schnell, vermutlich - auch wenn ND4J die zugrunde liegende Hardware abstrahiert und native C++-Bibliotheken nutzt.

Ich würde klein anfangen. Vielleicht ein 50x50 Netz entwerfen, das nur einen Buchstaben klassifizieren kann. Dann geht's weiter.
 
Tut mir leid, wenn ich der Spielverderber bin, aber: Java und maschinelles Lernen sind einfach nicht kompatibel. Das winzige Stückchen Python-Wissen, welches man braucht um produktiv zu sein, ist so wenig im Vergleich dazu wie viel man über NN/ML insgesamt lernen kann. In Java
Du musst dir dazu nur eine Hilfsmethode schreiben, zum Beispiel, um eine nxm-Matrix mit einer mxn-Matrix zu multiplizieren:

Java:
    private List<List<String>> multiply(List<List<String>> a, List<List<String>> b) {
        List<List<String>> c = new ArrayList<>();
       
    }
Das ist nicht dein Ernst? Solch ein Code sollte nicht existieren, nicht einmal wenn es drum geht, wie man das theoretisch machen könnte...

Es geht um Performance. Ständiges String-Parsen und toString-Gedöns macht alles um mehrere Größenordnungen langsamer. Außerdem möchte man natürlich optimierten SIMD-Code für solche Operationen nutzen.
 
Das Ding ist das ich keine Lust habe ein Teil in Python (die KI) und den Rest(z.B. die GUI) in Java zu schreiben. Auf ne Python-tkinter-gui z.B. habe ich kein Bock. Ich möchte das ganze Projekt in Java umsetzen. Wo liegt den das Problem bei ANNs in Zusammenhang mit Java?
 
Jo. Hab mich etwas informiert und Tensorflow Java sieht gut aus - das ist ein Tensorflow wrapper und man kommt komplett ohne Python aus - schreibt alles in Java. Es enthält die ndarray-Bibliothek die als Äquivalent zu numpy gilt - was ja mein ursprüngliches Problem war.
 
Tensorflow Python und Tensorflow Java sollen übrigens gleich schnell sein - es sind beides Wrapper für das in c++ geschriebene Tensorflow. Aber Java schlägt meiner Meinung nach Python in Performance, GUI- und Serverprogrammierung.
 
Ich glaube, die wichtigen Fragen wären, soll es erst einmal einfach "nur" funktionieren oder soll es gleichzeitig auch schnell sein. Ich würde eine stufenartige Herangehensweise wählen, aber ich kenne deinen Kenntnisstand nicht.

Bei mir ists so, dass ich lieber schnell kleine Erfolge sehen will, anstatt die Architektur aufzublähen.
 
Lustig, habe gerade ein LSTM RNN ANN mithilfe von deeplearning4j und nd4j und shadowJar implementiert (ihr könnt euch bestimmt denken, wieso) und das entstandene jar bzw. zip File ist über 1 GB groß und enthält mehr als 32.000 Entries... Wer es nicht glaubt, bitte selber überzeugen:

Code:
dependencies {
    // Logging...
    implementation "org.apache.logging.log4j:log4j-core:$log4jVers"
    implementation "org.apache.logging.log4j:log4j-plugins:$log4jVers"
    implementation "org.apache.logging.log4j:log4j-config-properties:$log4jVers"
    implementation 'org.slf4j:slf4j-simple:2.1.0-alpha1'

    // Die Deeplearning4j Kernbibliothek
    implementation 'org.deeplearning4j:deeplearning4j-core:1.0.0-M2.1'
    // Das CPU-Backend für die mathematischen Berechnungen (ND4J)
    implementation 'org.nd4j:nd4j-native:1.0.0-M2.1'
    // Plattform-spezifische Binärdateien für das CPU-Backend (automatische Erkennung)
    implementation 'org.nd4j:nd4j-native-platform:1.0.0-M2.1'
}


jar.enabled = false
tasks.named('distZip').configure { enabled = false }
tasks.named('distTar').configure { enabled = false }
tasks.named('startScripts').configure { enabled = false }

tasks.shadowJar {
    setProperty("zip64", true)

    duplicatesStrategy = DuplicatesStrategy.INCLUDE
    mergeServiceFiles()
}

Ich bin mir noch nicht sicher, weshalb das zip file so groß ist... Die genaue LSTM-Implementierung ist aber "geheim" und darf ich deshalb nicht zeigen.
 
Ich glaube, die wichtigen Fragen wären, soll es erst einmal einfach "nur" funktionieren oder soll es gleichzeitig auch schnell sein. Ich würde eine stufenartige Herangehensweise wählen, aber ich kenne deinen Kenntnisstand nicht.

Bei mir ists so, dass ich lieber schnell kleine Erfolge sehen will, anstatt die Architektur aufzublähen.
Hallo,
Ich möchte erstmal einfach das Python Buch durcharbeiten und den Python-Code in Java umsetzen. Ich befolge deinen Rat und baue erstmal kleinere Strukturen auf.
 
Ich dachte, Java sollte als "Middleware" dienen? 🤔 Jetzt doch nicht? Ich bin gerade erst mit Dl4j warm geworden.

Hatte auch schon das erste Speicherleck, da von C allokierter Arbeitsspeicher nicht automatisch wieder freigegeben wurde. (Ctor und dtor lassen grüßen.)
 
Alles in Ordnung. Mir geht es eigentlich die ganze Zeit darum, dass das Buch Python-Code verwendet und mit numpy und Tensorflow arbeitet. Dafür habe ich was Java kompatibles gesucht und gefunden. Das ist mir aber alles sehr kompliziert und ich muss mich dort erst noch reinfinden. Teilweise hapert es auch einfach an meinem mathematischen Verständnis. Ich habe mir jetzt auch zusätzlich noch ein Mathematikbuch besorgt.
 
Hatte auch nur eine Künstliche Intelligenz-Vorlesung. Es gibt in der Domäne jedoch Formeln, die mehr oder weniger als gegeben und gleichzeitig als ausreichend funktionierend angesehen werden, und somit keiner weiteren mathematischen Abklärung oder Begründung bedürfen... Sobald du über ~ "Sigmoid" stolperst, bist du auf dem richtigen Weg. ☺
 
Erstmal wollte ich den numpy Code in ND4J schreiben. Das war auch die Kernfrage des Threads - der Rest ist noch recht weit ausgeholt
Idee: Wenn es erstmal nur um die API geht und die Laufzeit keine Rolle spielt, sollte die auch ein LLM relativ leicht alles generieren können. Also quasi die Java-Sicht auf NumPy. Der Vorteil wäre, dass du selbst etwas über die Implementierung von Algorithmen lernst -- wenn dich das interessiert. Es wäre auch interessant aus Sicht eines API-Designers, weil NumPy überladene Operatoren verwendet, die Java ja natürlich so nicht hat. Also müsste so was intelligent nachgebildet werden. Logischerweise nur als Lernprojekt sehen. Produktiv wäre das sicherlich keine gute Idee; die modernen Bibliotheken nutzen alle SIMD oder sogar die GPU auf der Hardware-Seite und können dadurch Berechnungen vielfach beschleunigen.
 
Naja, das ist ja nicht soooo dramatisch. Das Modell besteht aus ein paar Hyperparametern und ein paar (fully connected) Layern. Die Layer haben eine gewisse Zahl an Neuronen, dazu Gewichte und Biases und natürlich die Aktivierungsfunktion.

Das kann man erstmal ganz langsam angehen:

Java:
interface ActivationFunction {
    float forward(float input);
    float derivative(float input);
}

Dazu zwei Implementierungen - ReLU für die Hidden Layer und Sigmoid für den OutputLayer für die binäre Klassifikation. Hier mal ReLU:

Java:
public class  ReLUActivation implements ActivationFunction {
    @Override
    public float forward(float input) { return input < 0 ? 0 : input; }
    @Override
    public float derivative(float input) { return input > 0 ? 1 : 0; }
}

Jetzt noch den Layer:
Java:
interface Layer {
    float[] forward(float[] inputs);
    int getOutputSize();
}
und eine Implementierung:
Java:
public class DenseLayer implements Layer {
    private final int inputSize;
    private final int outputSize;
    private final ActivationFunction activation;

    private float[] weights;
    private float[] bias;

    public DenseLayer(int inputSize, int outputSize, ActivationFunction activation) {
        this.inputSize = inputSize;
        this.outputSize = outputSize;
        this.activation = activation;

        weights = new float[inputSize * outputSize];
        bias = new float[outputSize];
 
        Random random = new Random();
        for (int i = 0; i < weights.length; i++) {
            weights[i] = (float) random.nextGaussian();
        }
    }           

    // für Tests und das Laden aus Dateien...
    public DenseLayer(float[] weights, float[] bias, ActivationFunction activation) {
        this.inputSize = weights.length / bias.length;
        this.outputSize = bias.length;
        this.activation = activation;
        this.weights = weights;
        this.bias = bias;
    }

    @Override
    public float[] forward(float[] inputs) {
        float[] output = new float[outputSize];
        int w = 0;
        for (int i = 0; i < output.length; i++) {
            float sum = bias[i];
            for (int j = 0; j < inputs.length; j++) {
                sum += weights[w] * inputs[j];
                w++;
            }
            output[i] = activation.forward(sum);
        }
        return output;
    }   

    @Override
    public int getOutputSize() { return outputSize; }
}

Naja, und dann baut man sich halt noch das Netzwerk:
Java:
public class NeuralNetwork {
    private final List<Layer> layers = new ArrayList<>();
    private final int inputSize;

    public NeuralNetwork(int inputSize) {
        this.inputSize = inputSize;
    }

    public void addDenseLayer(int neurons, ActivationFunction activation) {
        int layerInputs = layers.isEmpty() ? inputSize : layers.getLast().getOutputSize();
        layers.add(new DenseLayer(layerInputs, neurons, activation));
    }

    public float[] forward(float[] inputs) {
        if (inputs.length != inputSize) throw new IllegalArgumentException();
        float[] output = inputs;
        for (Layer layer : layers) {
            output = layer.forward(output);
        }
        return output;
    }
}

Damit sollte die Forward-Strecke vollständig sein. Als nächstes noch eine LossFunction und Backpropagation einbauen (das spare ich mir jetzt). Und wenn man will, könnte man sich noch den Spaß machen, und den Code auf die Vector API umstellen, dann sollte das auch etwas schneller werden.
 
Sigmoid für den OutputLayer
Adam wäre als Updater besser

(float) random.nextGaussian();
Xavier wäre als Randomizer besser

float forward(float input);
double wäre für die Weights besser

Zum Beispiel Quadratic oder Hellinger

Zum Beispiel Cross entropy

Und dann fehlt noch eine Squared ridge regression (L2) und ein First-layer dropout sowie eine Feature normalization

Und dann muss das Ganze natürlich noch feinjustiert werden...

auf die Vector API umstellen
Dann wäre es schnell

Fertig ist das Gehirn 🙂
 
@dreckKopf kann man alles einbauen, das Training hatte ich ja noch nicht im Code. Sigmoid wird allerdings als Aktivierungsfunktion verwendet, Adam wäre ein Optimierer, genauso wie SGD. Die Ridge Regression wäre dann eine Option für den Optimierer. Normalisierung kann man über einen Input-Layer erledigen (darum das Layer-Interface).

Was die Initialisierung betrifft, hast Du einen echten Punkt. Der im Code enthaltenen, einfachen Initialisierung fehlt die Skalierung. Das funktioniert ggf. noch bei kleinen Netzen mit wenigen Neuronen, ansonsten ist der Bereich zu groß. Andererseits würde man für ReLU-Layer auch nicht Xavier verwenden, sondern He-Initialisierung, weil die Hälfte der Werte bei ReLU abgeschnitten werden.

Das Schöne ist, dass sich das alles sehr gut entkoppeln lässt und man sich so sein Netz zusammenstöpseln kann, wie man möchte.

Was den Datentyp betrifft, würde ich nicht auf double setzen. Zunächst einmal profitiert das Ergebnis nicht stark von der höheren Präzision. Dafür verbrät man mit double doppelt so viel Speicher.

Jetzt könnte man natürlich argumentieren, dass der Speicherverbrauch keine Rolle spielt, schließlich geht es um die Geschwindigkeit. Wenn wir aber von Geschwindigkeit reden, dann kommen wir zwangsweise auf SIMD und da ist float klar im Vorteil.
 
Ich bezweifele, dass das bei neueren CPUs noch so ist... Unsere Halbleiterhersteller sind ja auch nicht doof
Warum bezweifelst Du das? Du kannst ja heute auch noch 16-Bit-Additionen auf den 64-Bit-Registern durchführen und wenn Du willst, steckst Du in ein 64-Bit-Register z. B. zwei 31-Bit-Werte rein und führst mit einer 64-Bit-Addition eben zwei 32-Bit-Additionen auf einmal durch. Und die Intel Befehlssätze für SIMD können zusätzlich auch noch FP16.

Echte/heutige ANNs haben oft sehr kleine Mantissen, mit denen sie rechnen
Lass uns einfach mal die KI Fragen:

Google KI hat gesagt.:
In der modernen Künstlichen Intelligenz gibt es praktisch kein neuronales Netzwerk, das für das normale Training oder die Inferenz FP64 (Double Precision) benötigt. Formate wie FP32, BF16 oder FP8 sind hier der Standard, da die intrinsische Fehlertoleranz von KI-Modellen keine extreme mathematische Präzision verlangt.
Es gibt jedoch hochspezifische Ausnahmen an den Schnittstellen von KI und Wissenschaft, bei denen FP64 zwingend erforderlich ist:

1. Physics-Informed Neural Networks (PINNs)
[...]
2. KI für Klimaforschung und Meteorologie
[...]
3. Molekulardynamik und Quantenchemie (z. B. Proteinfaltung)
[...]
4. Extrem sensitive Reinforcement Learning (RL) Umgebungen
In der Luft- und Raumfahrt oder bei der Steuerung von Teilchenbeschleunigern werden RL-Agenten eingesetzt.
[...]

Zusammenfassung für die Praxis
Wenn du klassische Computer Vision (CNNs), Sprachverarbeitung (LLMs wie GPT/Llama) oder Tabulardaten (MLP) trainierst, ist FP64 reine Ressourcenverschwendung. Du verlierst 50 % bis 98 % der Hardwareleistung, ohne jeglichen Gewinn an Modellqualität.

Und ich glaube kaum, dass in den genannten Bereichen jemand ein ANN mit Java und der Vector API, die ja immer noch nicht final ist, aufbaut 😉
 
Doch, an einem der vier genannten Punkte arbeite ich gerade... verständlicherweise darf ich aber nicht mehr sagen. (Ok, der Teilchenbeschleuniger ist es nicht 😀)

Vom Java-Bashing ohne Grund halte ich gar nix...
 

Zurück
Oben