Machine Learning in Lyx

Lyx bringt maschinelles Lernen als native Bibliothek mit — kein Python, kein TensorFlow, keine externen Abhängigkeiten. Alle Algorithmen sind direkt in Lyx implementiert und laufen als normaler nativer Code auf x86_64 und ARM64.

Zwei Units decken den ML-Bereich ab:

Unit Inhalt Einsatz
std.ml Klassische ML-Algorithmen (Regression, Klassifikation, Clustering) Vorhersagen, Anomalieerkennung, Segmentierung
std.fasttext FastText-Wortvektor-Engine (Skip-gram, CBOW) NLP, semantische Suche, Textklassifikation

Lineare Regression

Lineare Regression sucht die Gerade y = w·x + b, die am besten zu einer Menge von (x, y)-Wertepaaren passt. Sie minimiert den mittleren quadratischen Fehler (MSE) per Gradient Descent. Einsatz: Preis- und Nachfragevorhersagen, Trendanalyse, kontinuierliche Ausgabewerte.

import std.ml;
import std.io;

fn main(): int64 {
    // Trainingsdaten: Hausgröße (m²) → Preis (€1000)
    var x: f64[6] := [40.0, 60.0, 80.0, 100.0, 120.0, 140.0];
    var y: f64[6] := [90.0, 130.0, 175.0, 220.0, 260.0, 310.0];

    // Modell initialisieren und trainieren
    LinearRegressionInit();
    LinearRegressionFitArrays(x as int64, y as int64, 6);

    // Ergebnis ausgeben
    PrintStr("Gewicht (w): "); PrintF64(LinearRegressionWeight()); PrintStr("\n");
    PrintStr("Bias    (b): "); PrintF64(LinearRegressionBias());   PrintStr("\n");
    PrintStr("MSE-Loss:    "); PrintF64(LinearRegressionLoss());    PrintStr("\n");
    PrintStr("Epochen:     "); PrintInt(LinearRegressionEpochs()); PrintStr("\n");

    // Vorhersagen für neue Werte
    PrintStr("Preis 90m²:  "); PrintF64(LinearRegressionPredict(90.0));  PrintStr(" k€\n");
    PrintStr("Preis 150m²: "); PrintF64(LinearRegressionPredict(150.0)); PrintStr(" k€\n");

    return 0;
}

LinearRegressionFitArrays erwartet Zeiger auf f64-Arrays und die Anzahl der Punkte. Das Modell trainiert intern mit Gradient Descent bis zur Konvergenz.


Logistische Regression

Logistische Regression klassifiziert binäre Ergebnisse (0 oder 1). Sie wendet die Sigmoid-Funktion auf eine lineare Kombination an und liefert eine Wahrscheinlichkeit. Einsatz: Spam-Erkennung, Kreditrisiko, Krankheitsdiagnose (ja/nein), A/B-Testauswertung.

import std.ml;
import std.io;

fn main(): int64 {
    // Trainingsdaten: Stunden gelernt → Prüfung bestanden (0/1)
    var x: f64[8] := [0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0];
    var y: f64[8] := [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];

    LogisticRegressionInit();
    LogisticRegressionFitArrays(x as int64, y as int64, 8, 0.1, 1000);

    PrintStr("Genauigkeit: ");
    PrintF64(LogisticRegressionAccuracy() * 100.0);
    PrintStr("%\n");

    // Wahrscheinlichkeit und Klasse
    var prob: f64  := LogisticRegressionPredictProb(2.5);
    var label: int64 := LogisticRegressionPredict(2.5);
    PrintStr("2.5h — Wahrscheinlichkeit: "); PrintF64(prob * 100.0); PrintStr("%\n");
    PrintStr("2.5h — Vorhersage:         "); PrintInt(label); PrintStr("\n");

    return 0;
}

LogisticRegressionFitArrays nimmt Lernrate und Epochenanzahl als direkte Parameter — kein separater Konfigurationsschritt nötig.


k-Nearest Neighbors (KNN)

KNN klassifiziert einen neuen Punkt, indem es die k ähnlichsten Punkte im Trainingsdatensatz sucht und die häufigste Klasse unter ihnen zurückgibt. Kein eigentliches Training — alle Berechnung findet zur Inferenzzeit statt. Einsatz: Empfehlungssysteme, Bilderkennung, Anomalieerkennung in Sensordaten.

import std.ml;
import std.io;

fn main(): int64 {
    // Trainingsdaten: 2 Features (Temperatur, Luftfeuchtigkeit), 2 Klassen
    // Klasse 0 = trocken, Klasse 1 = feucht
    var features: f64[10] := [
        20.0, 30.0,   // Punkt 1
        22.0, 28.0,   // Punkt 2
        21.0, 35.0,   // Punkt 3
        35.0, 80.0,   // Punkt 4
        38.0, 85.0,   // Punkt 5
    ];
    var labels: int64[5] := [0, 0, 0, 1, 1];

    KNNInit(3);   // k=3 Nachbarn
    KNNFit(features as int64, labels as int64, 5, 2);

    // Vorhersage für neuen Punkt [25.0, 40.0]
    var query: f64[2] := [25.0, 40.0];
    var predicted: int64 := KNNPredict(query as int64);
    PrintStr("Vorhersage: "); PrintInt(predicted); PrintStr("\n");

    // Genauigkeit auf Trainingsdaten
    var score: f64 := KNNScore(features as int64, labels as int64, 5);
    PrintStr("Genauigkeit: "); PrintF64(score * 100.0); PrintStr("%\n");

    return 0;
}

KNNInit(k) gefolgt von KNNFit speichert den gesamten Trainingsdatensatz. KNNDistance und KNNManhattanDistance stehen für eigene Distanzberechnungen zur Verfügung.


K-Means Clustering

K-Means teilt einen Datensatz in k Gruppen (Cluster), sodass die Abstände innerhalb jedes Clusters minimiert werden. Es ist unüberwachtes Lernen — es gibt keine vorgegebenen Labels. Einsatz: Kundensegmentierung, Anomalieerkennung, Datenkompression, Vorstufe für andere Algorithmen.

import std.ml;
import std.io;

fn main(): int64 {
    // Datenpunkte: 3 natürliche Gruppen
    var data: f64[18] := [
         1.0,  1.0,   // Gruppe A
         1.5,  2.0,
         2.0,  1.5,
        10.0, 10.0,   // Gruppe B
        10.5,  9.5,
        11.0, 10.0,
         5.0,  8.0,   // Gruppe C
         5.5,  7.5,
         6.0,  8.5,
    ];

    // 3 Cluster, 2 Features pro Punkt
    KMeansInit(3);
    KMeansFit(data as int64, 9, 2, 3);

    PrintStr("Iterationen: "); PrintInt(KMeansGetIter());    PrintStr("\n");
    PrintStr("Inertia:     "); PrintF64(KMeansGetInertia()); PrintStr("\n");

    // Neuen Punkt klassifizieren
    var point: f64[2] := [1.8, 1.7];
    var cluster: int64 := KMeansPredictCluster(point as int64);
    PrintStr("Punkt [1.8, 1.7] → Cluster "); PrintInt(cluster); PrintStr("\n");

    // Alle Zuweisungen ausgeben
    var assignments: int64 := KMeansGetAssignments();
    var i: int64 := 0;
    while (i < 9) limit(9) {
        PrintStr("Punkt "); PrintInt(i);
        PrintStr(" → Cluster "); PrintInt((assignments + i * 8) as int64); // int64-Array
        PrintStr("\n");
        i := i + 1;
    }

    return 0;
}

KMeansInitCentroids kann alternativ zur manuellen Initialisierung der Startzentroiden verwendet werden, wenn eine deterministische Ausführung benötigt wird.


Entscheidungsbaum

Ein Entscheidungsbaum teilt den Merkmalsraum rekursiv nach dem Kriterium, das die Unreinheit am stärksten reduziert (Gini-Impurity, Entropie oder MSE für Regression). Einsatz: Regelbasierte Klassifikation, interpretierbare Modelle, medizinische Diagnose, Betrugserkennung.

import std.ml;
import std.io;

fn main(): int64 {
    // Iris-ähnlicher Datensatz: 4 Features, 3 Klassen
    var features: f64[20] := [
        5.1, 3.5, 1.4, 0.2,   // Klasse 0
        4.9, 3.0, 1.4, 0.2,   // Klasse 0
        7.0, 3.2, 4.7, 1.4,   // Klasse 1
        6.4, 3.2, 4.5, 1.5,   // Klasse 1
        6.3, 3.3, 6.0, 2.5,   // Klasse 2
    ];
    var labels: int64[5] := [0, 0, 1, 1, 2];

    // maxDepth=5, Gini-Kriterium
    DecisionTreeInit(5, DT_GINI);
    DecisionTreeFit(features as int64, labels as int64, 5, 4, 3);

    // Klassifikation
    var query: f64[4] := [6.5, 3.0, 4.8, 1.5];
    var predicted: int64 := DecisionTreePredict(query as int64);
    PrintStr("Vorhersage: Klasse "); PrintInt(predicted); PrintStr("\n");

    // Genauigkeit
    var preds: int64[5];
    var i: int64 := 0;
    while (i < 5) limit(5) {
        var row: f64[4] := [
            features[i*4], features[i*4+1], features[i*4+2], features[i*4+3]
        ];
        preds[i] := DecisionTreePredict(row as int64);
        i := i + 1;
    }
    var acc: f64 := AccuracyScore(labels as int64, preds as int64, 5);
    PrintStr("Genauigkeit: "); PrintF64(acc * 100.0); PrintStr("%\n");

    return 0;
}

Kriterien: DT_GINI (Standard für Klassifikation), DT_ENTROPY (informationstheoretisch), DT_MSE (für Regression).


Naive Bayes

Naive Bayes ist ein probabilistischer Klassifikator, der auf dem Satz von Bayes basiert und naiv annimmt, dass alle Features unabhängig voneinander sind. Trotz dieser Vereinfachung funktioniert er bei Textklassifikation oft überraschend gut. Einsatz: Spam-Filter, Stimmungsanalyse, Dokumentkategorisierung.

import std.ml;
import std.io;

fn main(): int64 {
    // Vokabular: [spam, gewinn, angebot, hallo, termin, meeting]
    // Dokument 0: [1, 1, 1, 0, 0, 0] → Klasse 0 (Spam)
    // Dokument 1: [0, 0, 0, 1, 1, 1] → Klasse 1 (Kein Spam)

    var doc0: int64[3] := [0, 1, 2];   // Wortindizes
    var doc1: int64[3] := [3, 4, 5];
    var docs: int64[2] := [doc0 as int64, doc1 as int64];

    var lengths: int64[2] := [3, 3];
    var labels:  int64[2] := [0, 1];

    // 2 Klassen, Vokabulargröße 6
    NaiveBayesInit(2, 6);
    NaiveBayesFit(docs as int64, lengths as int64, labels as int64, 2);

    // Klassifikation: neues Dokument mit "gewinn" und "angebot"
    var query: int64[2] := [1, 2];
    var predicted: int64 := NaiveBayesPredict(query as int64, 2);
    PrintStr("Vorhersage: Klasse "); PrintInt(predicted);
    PrintStr(predicted == 0 ? " (Spam)\n" : " (Kein Spam)\n");

    return 0;
}


Normalisierung und Datenvorbereitung

ML-Algorithmen reagieren empfindlich auf unterschiedliche Werteskalen. MinMaxNorm skaliert auf [0, 1], ZScoreNorm auf Mittelwert 0 und Standardabweichung 1. TrainTestSplit teilt Indices reproduzierbar auf.

import std.ml;
import std.io;

fn main(): int64 {
    // Rohdaten normalisieren
    var raw: f64[5] := [10.0, 25.0, 50.0, 75.0, 100.0];
    var min: f64 := 10.0;
    var max: f64 := 100.0;

    var i: int64 := 0;
    while (i < 5) limit(5) {
        var norm: f64 := MinMaxNorm(raw[i], min, max);
        PrintStr("MinMax: "); PrintF64(norm); PrintStr("\n");
        i := i + 1;
    }

    // Z-Score-Normalisierung
    var mean:   f64 := 52.0;
    var stddev: f64 := 33.0;
    i := 0;
    while (i < 5) limit(5) {
        var z: f64 := ZScoreNorm(raw[i], mean, stddev);
        PrintStr("Z-Score: "); PrintF64(z); PrintStr("\n");
        i := i + 1;
    }

    // Rückrichtung: aus normalisierten Werten ursprünglichen Wert berechnen
    var denorm: f64 := MinMaxDenorm(0.5, min, max);
    PrintStr("Denorm 0.5: "); PrintF64(denorm); PrintStr("\n");

    // Train/Test-Split (80/20): Index < 0.8 → Training, sonst Test
    i := 0;
    while (i < 10) limit(10) {
        var split: int64 := TrainTestSplit(i);
        PrintStr("Punkt "); PrintInt(i);
        PrintStr(split == 0 ? " → Train\n" : " → Test\n");
        i := i + 1;
    }

    return 0;
}


Evaluationsmetriken

Alle Metriken arbeiten auf Arrays und sind unabhängig vom Algorithmus verwendbar.

Funktion Bedeutung Einsatz
MeanSquaredError(y_true, y_pred, n) Mittlerer quadratischer Fehler — bestraft große Abweichungen stark Regression
MeanAbsoluteError(y_true, y_pred, n) Mittlerer absoluter Fehler — robuster gegenüber Ausreißern Regression
R2Score(y_true, y_pred, n) Bestimmtheitsmaß: 1.0 = perfekt, 0.0 = Mittelwert-Baseline Regression
AccuracyScore(y_true, y_pred, n) Anteil korrekt klassifizierter Punkte Klassifikation

import std.ml;
import std.io;

fn main(): int64 {
    var y_true: f64[4] := [10.0, 20.0, 30.0, 40.0];
    var y_pred: f64[4] := [12.0, 19.0, 31.0, 38.0];

    PrintStr("MSE: "); PrintF64(MeanSquaredError(y_true as int64, y_pred as int64, 4)); PrintStr("\n");
    PrintStr("MAE: "); PrintF64(MeanAbsoluteError(y_true as int64, y_pred as int64, 4)); PrintStr("\n");
    PrintStr("R²:  "); PrintF64(R2Score(y_true as int64, y_pred as int64, 4)); PrintStr("\n");

    var labels_true: int64[4] := [0, 1, 1, 0];
    var labels_pred: int64[4] := [0, 1, 0, 0];
    PrintStr("Acc: "); PrintF64(AccuracyScore(labels_true as int64, labels_pred as int64, 4) * 100.0); PrintStr("%\n");

    return 0;
}


FastText: Wortvektor-Engine

std.fasttext implementiert die FastText-Wortvektor-Engine (angelehnt an Facebook Research, 2016). Sie trainiert neuronale Einbettungsmodelle auf Textkorpora und erzeugt dichte Vektoren, die semantische Ähnlichkeiten kodieren — ähnliche Wörter liegen nah beieinander im Vektorraum.

Trainingsalgorithmen

Modell Funktionsweise Stärke
Skip-gram Lernt aus einem Wort, den Kontext vorherzusagen Seltene Wörter, kleine Korpora
CBOW Lernt aus dem Kontext, das mittlere Wort vorherzusagen Häufige Wörter, große Korpora, schneller

Training

import std.fasttext;
import std.io;

fn main(): int64 {
    // Hyperparameter setzen (optional — Defaults: dim=100, lr=0.025, window=5)
    FastTextSetDimension(50);     // kleinerer Vektor für dieses Beispiel
    FastTextSetLearningRate(0.05);
    FastTextSetWindow(3);

    // Vokabular-Speicher initialisieren (Korpusgröße schätzen)
    FastTextInitVocab(10000);

    // Training: Skip-gram, 50 Epochen
    // (corpus = Wortindex-Array, n = Anzahl Wörter)
    // var corpus: int64[...] := [...];
    // FastTextTrainSkipgramSGD(corpus as int64, corpus_size, DEFAULT_EPOCHS, NEG_SAMPLES);

    PrintStr("Dimension:    "); PrintInt(FastTextGetDimension()); PrintStr("\n");
    PrintStr("Vokabulargröße: "); PrintInt(FastTextGetVocabSize()); PrintStr("\n");

    // Modell speichern
    FastTextSaveModel("wordvectors.bin");

    return 0;
}

Semantische Suche (Nearest Neighbors)

Nach dem Training kann für jeden Wortvektor die Liste der k ähnlichsten Wörter abgerufen werden. Das ist die Basis für Empfehlungssysteme und semantische Suchmaschinen.

import std.fasttext;
import std.io;

fn main(): int64 {
    // Modell laden
    FastTextLoad("wordvectors.bin");

    // Vektor für "König" abrufen (Wortindex 42)
    var vec_koenig: int64 := FastTextGetEmbeddingAt(42);

    // 5 nächste Nachbarn suchen
    var nearest: int64 := FastTextFindNearest(vec_koenig, 5);
    // nearest zeigt auf ein int64-Array mit 5 Wortindizes

    PrintStr("Wörter ähnlich zu 'König': ");
    var i: int64 := 0;
    while (i < 5) limit(5) {
        var idx: int64 := (nearest + i * 8) as int64;
        PrintInt(idx);
        PrintStr(" ");
        i := i + 1;
    }
    PrintStr("\n");

    FastTextFree();
    return 0;
}

Wort-Analogien lösen

FastText-Modelle kodieren semantische Beziehungen als Vektordifferenzen. Die bekannte Analogie „König – Mann + Frau ≈ Königin„ ist direkt über FastTextAnalogies lösbar.

import std.fasttext;
import std.io;

fn main(): int64 {
    FastTextLoad("wordvectors.bin");

    // Wortindizes aus dem trainierten Vokabular
    con IDX_KOENIG:  int64 := 42;
    con IDX_MANN:    int64 := 17;
    con IDX_FRAU:    int64 := 28;

    // Löst: König – Mann + Frau = ?
    var vec_a: int64 := FastTextGetEmbeddingAt(IDX_KOENIG);
    var vec_b: int64 := FastTextGetEmbeddingAt(IDX_MANN);
    var vec_c: int64 := FastTextGetEmbeddingAt(IDX_FRAU);

    var result_idx: int64 := FastTextAnalogies(vec_a, vec_b, vec_c);
    PrintStr("König – Mann + Frau = Wortindex: ");
    PrintInt(result_idx);
    PrintStr("\n");

    FastTextFree();
    return 0;
}

Textklassifikation mit FastText

Ein aus Textvektoren zusammengesetzter Durchschnittsvektor kann direkt mit FastTextClassify einer Kategorie zugeordnet werden — ohne separaten Klassifikator.

import std.fasttext;
import std.io;

fn main(): int64 {
    FastTextLoad("classifier.bin");

    // Textvektor aus Wortindizes aufbauen (vereinfacht: erster Wortvektor)
    var text_vec: int64 := FastTextGetEmbeddingAt(5);
    FastTextNormalize(text_vec);   // Auf Einheitslänge normalisieren

    var label: int64 := FastTextClassify(text_vec);
    var prob:  f64   := FastTextClassifyProb(text_vec, label);

    PrintStr("Klasse: "); PrintInt(label); PrintStr("\n");
    PrintStr("Konfidenz: "); PrintF64(prob * 100.0); PrintStr("%\n");

    FastTextFree();
    return 0;
}


Vollständiges Beispiel: Hauspreismodell

Dieses Beispiel kombiniert Normalisierung, Training, Evaluation und Vorhersage in einem vollständigen Workflow.

import std.ml;
import std.io;

fn main(): int64 {
    // ── Rohdaten ──────────────────────────────────────────────────────────────
    // Feature: Wohnfläche m²
    var x_raw: f64[10] := [35.0, 50.0, 65.0, 80.0, 95.0, 110.0, 130.0, 150.0, 170.0, 200.0];
    // Label: Preis in €1000
    var y_raw: f64[10] := [80.0, 120.0, 155.0, 195.0, 235.0, 270.0, 315.0, 365.0, 410.0, 480.0];

    // ── Normalisierung ────────────────────────────────────────────────────────
    con X_MIN: f64 := 35.0;
    con X_MAX: f64 := 200.0;
    con Y_MIN: f64 := 80.0;
    con Y_MAX: f64 := 480.0;

    var x_norm: f64[10];
    var y_norm: f64[10];
    var i: int64 := 0;
    while (i < 10) limit(10) {
        x_norm[i] := MinMaxNorm(x_raw[i], X_MIN, X_MAX);
        y_norm[i] := MinMaxNorm(y_raw[i], Y_MIN, Y_MAX);
        i := i + 1;
    }

    // ── Training (80% = 8 Punkte) ─────────────────────────────────────────────
    LinearRegressionInit();
    LinearRegressionFitArrays(x_norm as int64, y_norm as int64, 8);

    PrintStr("Modell trainiert:\n");
    PrintStr("  w = "); PrintF64(LinearRegressionWeight()); PrintStr("\n");
    PrintStr("  b = "); PrintF64(LinearRegressionBias());   PrintStr("\n");

    // ── Evaluation auf Test-Daten (letzten 2 Punkte) ──────────────────────────
    var y_pred: f64[2];
    i := 0;
    while (i < 2) limit(2) {
        var x_test: f64 := x_norm[8 + i];
        y_pred[i] := LinearRegressionPredict(x_test);
        i := i + 1;
    }

    var mse: f64 := MeanSquaredError(
        y_norm as int64 + 8 * 8,   // Zeiger auf y_norm[8]
        y_pred as int64,
        2
    );
    PrintStr("Test-MSE (normalisiert): "); PrintF64(mse); PrintStr("\n");

    // ── Vorhersage mit Denormalisierung ───────────────────────────────────────
    var query_m2: f64 := 115.0;
    var query_norm: f64 := MinMaxNorm(query_m2, X_MIN, X_MAX);
    var pred_norm: f64 := LinearRegressionPredict(query_norm);
    var pred_price: f64 := MinMaxDenorm(pred_norm, Y_MIN, Y_MAX);

    PrintStr("Vorhersage 115 m²: ");
    PrintF64(pred_price);
    PrintStr(" k€\n");

    return 0;
}


Einheiten-Referenz