No description
  • Python 95.7%
  • CSS 1.7%
  • HTML 1.5%
  • JavaScript 1.1%
Find a file
Tess (KI-Assistentin) 17eee7fdf6 feat(i18n): Sprachen automatisch aus translations/ erkennen, EN ergänzen, AR entfernen
- i18n.py: supported_langs() scannt translations/*.json
- __init__.py: context_processor injectiert supported_langs + current_lang
- templates: Lang-Switch rendert dynamisch aus supported_langs
- translations/en.json: Web-Keys (login, register, settings, auth, session)
- translations/ar.json: gelöscht
2026-08-24 21:27:46 +00:00
data feat(web): Session-DB, i18n, Session-Policies, Settings-UI 2026-08-24 21:22:02 +00:00
HyperParameter refactor: __init__.py-Pakete vervollständigt, Imports bereinigt 2026-08-21 22:14:59 +00:00
Model refactor: __init__.py-Pakete vervollständigt, Imports bereinigt 2026-08-21 22:14:59 +00:00
Server feat(i18n): Sprachen automatisch aus translations/ erkennen, EN ergänzen, AR entfernen 2026-08-24 21:27:46 +00:00
tests refactor: __init__.py-Pakete vervollständigt, Imports bereinigt 2026-08-21 22:14:59 +00:00
Trainer refactor: __init__.py-Pakete vervollständigt, Imports bereinigt 2026-08-21 22:14:59 +00:00
translations feat(i18n): Sprachen automatisch aus translations/ erkennen, EN ergänzen, AR entfernen 2026-08-24 21:27:46 +00:00
.gitignore feat(web): Session-DB, i18n, Session-Policies, Settings-UI 2026-08-24 21:22:02 +00:00
conftest.py Rollout PyTorch Layers 2026-08-16 23:38:21 +02:00
Errorcodes.md refactor(api): einheitliche Node-Source/Target-API für ModelBuilder und Trainer 2026-08-21 21:14:05 +00:00
ideas.md fix(controlflow): IfSample pro-Sample-Mixer für dynamische Ifs, If bleibt batch-weit für statische 2026-08-21 11:51:05 +00:00
manual_test.py refactor: __init__.py-Pakete vervollständigt, Imports bereinigt 2026-08-21 22:14:59 +00:00
manual_test2.py refactor(api): einheitliche Node-Source/Target-API für ModelBuilder und Trainer 2026-08-21 21:14:05 +00:00
manual_test_gan.py Trainer: Nodes für Feedforward bis GAN erweitert 2026-08-21 21:31:54 +00:00
manual_test_llm.py refactor: __init__.py-Pakete vervollständigt, Imports bereinigt 2026-08-21 22:14:59 +00:00
README.md refactor(api): einheitliche Node-Source/Target-API für ModelBuilder und Trainer 2026-08-21 21:14:05 +00:00
requirements-core.txt Chore: requirements.txt + requirements-core.txt anlegen 2026-08-17 15:46:54 +00:00
requirements.txt feat(web): Session-DB, i18n, Session-Policies, Settings-UI 2026-08-24 21:22:02 +00:00
test_activationfunctions.py refactor(api): einheitliche Node-Source/Target-API für ModelBuilder und Trainer 2026-08-21 21:14:05 +00:00

ML_Projekt

⚠️ Dieses README ist vorläufig und KI-generiert. Es wurde von einer KI auf Basis des Repository-Stands am 17.08.2026 erstellt und ist noch nicht durch einen Menschen geprüft worden. Code-Zitate, Zahlen (z. B. die Anzahl der Layer-Adapter) und API-Beispiele können fehlerhaft oder veraltet sein – bitte vor der Verwendung gegen den aktuellen Code und den Betreuer des Repositories gegenprüfen.

Ein eigenes, deklaratives Neural-Network-Framework auf PyTorch-Basis. Statt Netze imperativ zusammenzustecken, beschreibt man die Topologie als gerichteten Graphen aus Sources und Targets – aus diesem Graphen baut ModelBuilder.build() dann das ausführbare nn.Module (inkl. Backprop).

Kernidee

Drei Bausteine, sauber getrennt:

  1. Layer-Adapter (Model/layer/) – jeder Adapter kapselt genau einen PyTorch-Baustein (nn.Linear, nn.Conv2d, …) hinter einer einheitlichen Layer-Basisklasse. generate_layer() liefert den fertigen nn.Module. 174 Adapter, u. a. für:

    • Aktivierungen (activations.py)
    • Linear & Bilinear (linear.py)
    • Conv / ConvTranspose 1D/2D/3D (conv.py)
    • Max/Avg/Adaptive/Global/LP-Pooling (pooling.py, maxpool.py)
    • Normalisierung: Batch / Sync-Batch / Layer / Group / Instance (normalization.py)
    • Rekurrent: RNN, LSTM, GRU (inkl. Cells) (recurrent.py)
    • Tensor-Operationen: Reshape, Split/Concat, MatMul, Elementwise, Dim-Reduktion (tensoroperation.py)
    • Dropout-Varianten (dropout.py)
    • Loss-Funktionen als Layer: MSE, CrossEntropy, BCE, Focal, Dice, … (loss.py)
    • Attention, Embedding (attention.py)
    • Sonstiges: Upsample, Padding, PixelShuffle, OneHot, Fold/Unfold (misc.py)
  2. Graph-Building (Model/model_builder.py) – Kanten werden als (Source, Target)-Paare definiert:

    Source Bedeutung
    InputSource(id) Modell-Eingang, id = Position des Arguments in forward(*x)
    NodeSource(id, output_id) Einzel-Ausgang eines Layers
    NodeSource(id, port, output_id) ein bestimmter Output-Port eines Multi-Output-Layers

    id = Position im Builder, nicht ein Name: Alle Klassen tragen dieselben Attribute wie die Kanten-Tabelle, wobei id immer die Position im ModelBuilder meint – die Reihenfolge der add_node()-Aufrufe bei NodeSource/NodeTarget, die Reihenfolge der add_input()-Aufrufe bei InputSource und die Reihenfolge der add_output()-Aufrufe bei OutputTarget (die Listen mb.inputs / mb.outputs definieren damit die Signatur von forward(*x)).

    Target Bedeutung
    NodeTarget(id, input_id) Eingang eines Layers. input_id = zeitliche Re-Nutzung (RNN-artig: fc → act → fc ruft dieselbe Schicht mehrfach auf) – nicht parallele Eingänge
    NodeTarget(id, port, input_id) parallele Eingänge (z. B. Bilinear, Concat), port = welche Eingangsposition
    OutputTarget(id) Modell-Ausgang

    add_input(name) / add_output(name) registrieren die Modell-Eingänge/-Ausgänge in ihrer Position – add_input("x") ist Argument 0 von forward(*x), add_input("y") Argument 1; ein einziger Modell-Output wird als Tensor zurückgegeben, mehrere als sortiertes Tuple.

  3. Hyperparameter-System (HyperParameter/) – Parameter werden deklarativ als Hyperparameter-Objekte durchgereicht statt als Konstanten:

    • TunableHyperparameter – manuell gesetzter Wert
    • SearchableHyperparameter – Wert mit dtype, min, max, stepsize für automatisierte Suche
    • HyperParameters – Container; verbindet Parameter mit einer Suchstrategie, iteriert Populationen via step() / push()
    • HyperParameterSearch – Basisklasse der Suche (ask/tell-Interface)
    • GridSearch – erste konkrete Implementierung

    Damit kann man ein Modell bauen, dessen Architekturentscheidungen (Abmessungen, Bias an/aus, …) im Trainingslauf automatisch durchsucht werden.

Quickstart

import torch
from HyperParameter.hyperparameter import TunableHyperparameter
from Model.model_builder import (
    ModelBuilder,
    InputSource,
    OutputTarget,
    NodeTarget,
    NodeSource
)
from Model.layer.linear import Linear
from Model.layer.activations import ReLU

hin  = TunableHyperparameter("in", 5)
hout = TunableHyperparameter("out", 2)
bias = TunableHyperparameter("bias", True)

fc  = Linear(hin=hin, hout=hout, hbias=bias)
rel = ReLU()

mb = ModelBuilder()
mb.add_node(fc)
mb.add_node(rel)
mb.add_input("input")
mb.add_output("output")

# Eingang(0) → fc → ReLU → Ausgang
mb.add_edge(InputSource(0), NodeTarget(0))
mb.add_edge(NodeSource(0), NodeTarget(1))
mb.add_edge(NodeSource(1), OutputTarget(0))

Model = mb.build()
print(Model(torch.tensor([1., 2., 3., 4., 5.])))

Ein vollständiges (größeres) Beispiel liegt in manual_test.py.

Quellen & Senken: die Source- und Target-Klassen im Detail

Jede Kante ist ein Tupel (source, target). Die Basisklassen Source und Target tragen nur das gemeinsame Attribut id; die konkreten Typen fassen die zusätzlichen Attribute zusammen:

Klasse Konstruktor type-Attribut Zusätzliche Attribute
InputSource (id) "InputSource" –
NodeSource (id, output_id=0) "NodeSource" output_id
NodeSource (id, port, output_id=0) "NodeSource" port, output_id
NodeTarget (id, input_id=0) "NodeTarget" input_id
NodeTarget (id, port, input_id=0) "NodeTarget" port, input_id
OutputTarget (id) "OutputTarget" –

Attribute: id, input_id, output_id, port

  • output_id / input_id (Aufruf-Runde): ein Layer kann im selben forward mehrmals mit geteilten Gewichten aufgerufen werden (Weight Sharing). input_id markiert, in welcher Runde der Layer ausgeführt wird; die Ausgabe dieses Aufrufs landet im internen Wertespeicher unter (layer_id, input_id) und wird ausschließlich von NodeSource(layer_id, output_id=input_id) gelesen. Default 0 deckt den gewöhnlichen Ein-Aufruffall ab – ein Ziel-input_id, zu dem nie eine passende NodeSource gehört (bzw. umgekehrt), bleibt unproduziert und wird als Deadlock abgefangen.
  • port (Multi-Output): welcher Ausgabewert eines Layers, wenn es ein Tuple/List zurückliefert (Split2 → Port 0 = erste Hälfte, Port 1 = zweite). Intern wird jeder Wert unter (layer_id, output_id, port) adressiert. Nur bei NodeSource vorhanden.
  • port (Multi-Input): welche Eingangsposition eines Layers, z. B. Bilinear(x1, x2) → Port 0 = x1, Port 1 = x2. Nur bei NodeTarget vorhanden. Die Engine puffert die eingehenden Tensoren pro Port und ruft den Layer genau dann auf, wenn range( layer.num_inputs) vollständig besetzt ist – Port-Reihenfolge = Positionsargumente von forward, bei gerichteten Operationen (Sub, Div, Atan2) also inhaltlich relevant.

Wichtig: NodeTarget und NodeTarget sind zwei unterschiedliche Dinge mit überlappenden Namen. input_id an einer NodeTarget bedeutet zeitliche Re-Nutzung (nacheinander, gleiche Gewichte), port an einer NodeTarget bedeutet parallele Eingänge (gleichzeitig, ein Aufruf). Nicht verwechseln.

Wie die Engine den Graphen ausführt

ModelBuilder.build() liefert ein nn.Module. forward arbeitet in zwei Phasen:

  1. Seed-Phase: Alle (InputSource → *)-Kanten werden sofort verarbeitet – die Argumente von forward(*x) fließen in die Graphen ein.
  2. Fixpunkt-Phase: In jeder Runde werden alle (Layer-Source → *)-Kanten geprüft, deren Quelle einen Wert im internen Speicher hat; deren Target wird daraufhin ausgeführt. Die Runde wiederholt sich, bis alle OutputTargets besetzt sind. Bleibt nach einer ganzen Runde nichts Neues passieren → Deadlock (ML-200203): ein Ziel wartet auf einen Wert, der nie produziert wird.

Daraus folgt: Die Reihenfolge der add_edge()-Aufrufe ist irrelevant, die Engine leitet die Execution-Order allein aus den Abhängigkeiten ab. Umgekehrt: jede Quelle muss einen Wert liefern können (dessen Target ausgeführt wird) und jede produzierte Ausgabe muss gelesen werden (via Layer*OutputSource oder OutputTarget) – sonst bleibt etwas unproduziert bzw. unverbraucht und wird als Deadlock abgefangen.

Weight Sharing: dieselbe Schicht, mehrere Aufrufe

Weight Sharing entsteht, wenn dieselbe Layer-Instanz in mehreren input_id-Runden aufgerufen wird. Jede Runde hat eine eigene input_id; die Ausgabe dieser Runde wird durch die output_id der NodeSource selektiert. Die Gewichte werden pro Vorwärts-Pass nicht dupliziert; PyTorch akkumuliert die Gradienten aller Aufrufe über dieselben Parameter – Parameter-Sparnis gegenüber zwei separaten Schichten, mit dem Preis, dass beide Stufen zwingend dieselbe Transformation lernen.

# fc → ReLU → fc, wobei fc beide Male dieselbe Instanz ist (geteilte Gewichte)
fc  = Linear(hin=th("in", 8),  hout=th("out", 8), hbias=th("bias", True))
act = ReLU()

mb = ModelBuilder()
mb.add_node(fc)     # 0 – wird 2× aufgerufen: input_id 0 und 1
mb.add_node(act)    # 1
mb.add_input("in")
mb.add_output("out")

# Runde 0: Input → fc(input_id=0) → ReLU
mb.add_edge(InputSource(0),    NodeTarget(0, input_id=0))
mb.add_edge(NodeSource(0),   NodeTarget(1))          # Ausgabe von fc(0)

# Runde 1: ReLU → fc(input_id=1) → Output
mb.add_edge(NodeSource(1),   NodeTarget(0, input_id=1))
mb.add_edge(NodeSource(0, output_id=1), OutputTarget(0))  # Ausgabe von fc(1)

model = mb.build()

# Referenz: fc(ReLU(fc(x))) mit derselben Instanz – dieselben Gewichte, 2× durchlaufen
x = torch.randn(1, 8)
assert torch.allclose(model(x), model.layers[0](torch.relu(model.layers[0](x))), atol=1e-5)

Beachte die Paare: NodeTarget(id, input_id) sagt, in welcher Runde die Schicht ausgeführt wird; NodeSource(id, output_id) sagt, welche der Runden ausgelesen wird. Im obigen Beispiel: output_id=1 an Layer 0 liefert das Ergebnis der zweiten Runde – das Modell-Output.

Ein Layer mit mehreren Eingängen (Multi-Input)

Adapter für parallele Eingänge (Bilinear, Add, Concat, MatMul, …) declarieren ihre Eingangsanzahl im Konstruktor via num_inputs (Standard 1): super().__init__(name, num_inputs=2). Der ModelBuilder liest num_inputs ausschließlich vom Adapter (nicht vom generierten nn.Module) – generierte Module müssen das Attribut nicht mitführen, nackte PyTorch-Instanzen als generate_layer()-Ergebnis sind damit erlaubt. Jeder Graphen-Eingang geht an einen eigenen NodeTarget-Port:

# x1 = fc_a(x),  x2 = fc_b(x),  out = x1 + x2  (Add hat num_inputs=2)
fc_a = Linear(hin=th("a", 8), hout=th("b", 4), hbias=th("bias", False))
fc_b = Linear(hin=th("c", 8), hout=th("d", 4), hbias=th("bias", False))
add  = Add()  # Tensor-Op: 2 Eingänge

mb = ModelBuilder()
mb.add_node(fc_a)   # 0
mb.add_node(fc_b)   # 1
mb.add_node(add)    # 2
mb.add_input("x")
mb.add_output("y")

mb.add_edge(InputSource(0),         NodeTarget(0))   # x → fc_a
mb.add_edge(InputSource(0),         NodeTarget(1))   # x → fc_b
mb.add_edge(NodeSource(0),        NodeTarget(2, port=0))  # fc_a → Port 0
mb.add_edge(NodeSource(1),        NodeTarget(2, port=1))  # fc_b → Port 1
mb.add_edge(NodeSource(2),        OutputTarget(0))

model = mb.build()

port ist die Position im Aufruf forward(*tensors) – bei symmetrischen Operationen (Add, Mul) spielt das keine Rolle, bei gerichteten (Sub(a, b), Div(a, b), Atan2(a, b)) bestimmt es das Ergebnis.

Ein Layer mit mehreren Ausgängen (Multi-Output)

Adapter, deren generate_layer() ein Tuple/List zurückliefert (Split2, SplitN, TopK, ArgMax → Wert+Index, …), haben ebenso viele Output-Ports wie Tuple-Elemente. Pro Port wird eine eigene NodeSource angelegt und kann an beliebige Targets weiterverdrahtet werden – auch an unterschiedliche NodeTarget-Ports, wie im Split→Concat-Roundtrip (tests/test_multi_output.py):

# Split2(x) → [x_h1, x_h2] → fc_a(x_h1), fc_b(x_h2) → (y_a, y_b)
split = Split2(hdim=th("d", 1))
fc_a  = Linear(hin=th("in", 4), hout=th("out", 2), hbias=th("bias", True))
fc_b  = Linear(hin=th("in", 4), hout=th("out", 3), hbias=th("bias", True))

mb = ModelBuilder()
mb.add_node(split)   # 0
mb.add_node(fc_a)    # 1
mb.add_node(fc_b)    # 2
mb.add_input("x")
mb.add_output("y_a")
mb.add_output("y_b")

mb.add_edge(InputSource(0),          NodeTarget(0))
mb.add_edge(NodeSource(0, port=0), NodeTarget(1))  # erste Hälfte → fc_a
mb.add_edge(NodeSource(0, port=1), NodeTarget(2))  # zweite Hälfte → fc_b
mb.add_edge(NodeSource(1),         OutputTarget(0))
mb.add_edge(NodeSource(2),         OutputTarget(1))

model = mb.build()
x = torch.randn(1, 8)
y_a, y_b = model(x)   # → Tuple, weil 2× add_output

Kurzübersicht

Szenario Was zu tun ist
1 Ein → 1 Aus NodeSource(id) → NodeTarget(id) (Defaults reichen)
Gewichtete Schicht, 2× aufgerufen input_id=0 und input_id=1 am Target; output_id an der Source selektieren
Parallel-N-Eingänge pro Kanten eine NodeTarget(id, port=k); Adapter deklariert num_inputs=N
Ein Layer → M-Outputs pro Output NodeSource(id, port=k)
Modell-Input → Layer InputSource(pos) → beliebiges Layer-Target; pos = Position in forward(*x)
Layer → Modell-Output Layer*OutputSource(...) → OutputTarget(pos); pos = Position im Rückgabe-Tuple

Installation & Tests

# Dependencies: Python ≥ 3.10
pip install -r requirements.txt        # Laufzeit + Tests
# pip install -r requirements-core.txt # nur Laufzeit (ohne pytest)

# Test-Suite (~1420 Tests)
pytest

Beachte: Die Tests müssen aus dem Projekt-Root heraus laufen (conftest.py legt das Projekt-Verzeichnis auf dem sys.path an).

Projektstruktur

├── Model/
│   ├── model_builder.py      # Graph-Modell: Sources, Targets, Build & Execution
│   ├── model.py              # (Legacy-Platzhalter)
│   ├── tests.py
│   └── layer/                # 174 Layer-Adapter, nach Funktion sortiert
├── HyperParameter/
│   ├── hyperparameter.py     # Hyperparameter, Searchable*, Tunable*
│   ├── hyperparameters.py    # Container + Search-Kopplung
│   ├── hyperparametersearch.py
│   └── searches/gridsearch.py
├── tests/                    # pytest-Suite
├── translations/
│   ├── en.json               # Referenzsprache (174 Einträge)
│   ├── de.json               # Deutsch (174 Einträge)
│   ├── sv.json               # Schwedisch (174 Einträge)
│   ├── ar.json               # Arabisch (174 Einträge)
│   └── generate_lang.py      # Übersetzungs-Generator (OpenAI-kompatible API)
├── requirements.txt          # Python-Abhängigkeiten (Laufzeit + Tests)
├── requirements-core.txt     # nur Laufzeit-Abhängigkeiten
├── conftest.py               # path-setup + Fixtures
├── manual_test.py            # durchlauffähiges Beispiel
└── Errorcodes.md             # Fehlercode-Referenz (ML-XXXXXX)

Layer-Infos & GUI-Kategorien

Alle mehrsprachigen Texte liegen zentral in translations/<lang>.json. Jede Adapter-Klasse trägt nur noch eine info_id (Key in der JSON-Datei). get_info(lang) (geerbt aus Layer) holt den Eintrag und rendert ihn. Neue Sprache = neue JSON-Datei anlegen, keine Python-Datei anfassen:

class ReLU(Layer):
    info_id = "relu"   # → Key in translations/de.json / sv.json / ar.json / …
from Model.layer.layer import available_languages
print(available_languages())  # {'en', 'de', 'sv', 'ar'} – ordnerdynamisch

layer.get_info("de")
# → {"Description": str,
#    "Example use case": str,
#    "Category": str,
#    "Mode": str,           # "Einfach" | "Vorgeschritten"  (Simple | Advanced)
#    "Params": {…}}

Ordnerdynamisch: get_info(lang) erkennt alle translations/*.json automatisch – neue Sprachdatei = neue verfügbare Sprache, ohne Python-Code. available_languages() liefert die Liste für GUI/CLI.

Neue Übersetzung erzeugen:

# Env-Vars setzen (Werte hier bewusst weggelassen – siehe .env oder Shell-Profil):
#   TRANSLATION_API_BASE  – z. B. http://<host>:<port>/v1
#   TRANSLATION_API_KEY   – Bearer-Key
#   TRANSLATION_MODEL     – Modell-Name
python3 translations/generate_lang.py de   # oder: sv, ar, fr, …

JSON-Struktur pro Eintrag (<info_id> als Key):

{
  "conv2d": {
    "description": "2D-Faltung: …",
    "use_case":    "Typisch für die ersten Schichten eines Bilderkenners (CNN).",
    "category":    "Faltung",
    "mode":        "Einfach",
    "params": {
      "hinc": "Anzahl der Eingabekanäle (z. B. 3 für RGB).",
      "hpadmode": {
        "description": "Polsterungsmodus.",
        "options": ["zeros", "reflect", "replicate", "circular"]
      }
    }
  }
}

"params"-Werte sind normalerweise einfache Strings. Ein Wert ist ein Objekt mit "description" + "options", wenn der Hyperparameter einen Integer-Index auf eine feste String-Liste abbildet (z. B. Polsterungsmodus). Die Liste steht in Index-Reihenfolge – get_info() rendert sie automatisch als "0" → "zeros", "1" → "reflect" … so dass die GUI ein Drop-Down daraus bauen kann.

Kategorie-Vokabular (stabil, für die GUI-Sortierung)

Jeder Adapter gehört zu genau einer Kategorie. Die Werte sind ein fixes Vokabular (DE/EN-Paare), damit die GUI sie sortieren/gruppieren kann:

de en Beispiele
Aktivierung Activation ReLU, Sigmoid, GELU
Linear Linear Linear, Bilinear
Faltung Convolution Conv1D/2D/3D, ConvTranspose*
Pooling Pooling MaxPool*, AvgPool*, Adaptive*, Global*
Normalisierung Normalization BatchNorm*, LayerNorm, GroupNorm, InstanceNorm*
Rekurrent Recurrent RNN, LSTM, GRU (inkl. Cells)
Attention Attention MultiHeadAttention, Embedding
Dropout Dropout Dropout*, AlphaDropout, FeatureAlphaDropout
Tensor-Op Tensor Op Reshape, Permute, Squeeze, CumSum, ArgMax …
Matrix-Op Matrix Op MatMul, BatchMatMul, Det, Inverse, Diag …
Verlust Loss MSELoss, CrossEntropyLoss, FocalLoss …
Up-/Downsample Resampling Upsample*, PixelShuffle, Padding*
Sonstiges Misc OneHot, Identity, Cast, Fold/Unfold

Regel: Neue Adapter bekommen nur eine der obigen Kategorien. Eine neue Kategorie nur, wenn wirklich nichts passt – und dann hier dokumentieren.

Status

  • ✅ Deklarative Graph-Topologie mit Multi-Input/Multi-Output und zeitlicher Re-Nutzung
  • ✅ 174 PyTorch-Layer als Adapter portiert und getestet
  • ✅ Mehrsprachige Layer-Infos: de, sv, ar (je 174 Einträge, ordnerdynamisch erkannt)
  • ✅ Hyperparameter-System inkl. GridSearch
  • ⬜ Automatisierte Suche: Feedback-Schleife (tell) noch nicht implementiert
  • ⬜ Training-Loop / Optimizer-Integration noch offen

→ Weitere Ideen und offene Punkte: ideas.md