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 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 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.
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 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.
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 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;
}
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;
}
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;
}
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.
| 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 |
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;
}
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;
}
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;
}
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;
}
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;
}