Piet Ostendorp, Marcel Anker, Lennart Heinrich
Der verwendete Machine Learning Algorithmus ist Naive Bayes.
Siehe python main.py Naive Bayes oder eigene Implementation in cargo run (src/*.rs).
Hinweis: Trotz Verwendung des StratifiedShuffleSplit und des Seeds 42 erzeugen scikit-learn (Python) und KNIME einen
unterschiedlichen Train/Test-Split.
Für die Implementierung in Python sowie Rust wurde der Split der Python-Implementierung verwendet (siehe shuffle.py).
Für Naive Bayes gibt es zum grundlegenden Ansatz kaum Einstellungen, Konfiguration oder Hyperparameter.
Je nach Implementierung kann eine Standard-Wahrscheinlichkeit (bei Features ohne Daten) oder Minimum-Standardabweichung (wenn sonst 0) gesetzt werden. Beides ist nicht relevant für die gegebenen Daten, da für alle Features Daten vorliegen und die Standardabweichung ungleich 0 ist.
Es können jedoch verschiedene Dichtefunktionen verwendet werden:
Der Algorithmus basiert auf dem Satz von Bayes:
-
$K$ : Klasse -
$F_i$ : Feature$i$ bzw. Merkmal$i$
Die Wahrscheinlichkeit
Unter Annahme der bedingten Unabhängigkeit gilt:
Die Klasse, bei der
Die Wahrscheinlichkeit
Die Wahrscheinlichkeiten
Es ist also eine Annäherung an die Häufigkeitsdichte notwendig. Hierfür kann z.B. eine Normalverteilung angenommen werden und die Dichtefunktion der Gaußschen Normalverteilung verwendet werden. Die Dichte an einer Stelle wird dann vereinfacht als
Alternativ kann eine Kernel Density Estimation verwendet werden, die den Verlauf der gegebenen Daten glättet.
Die Glättung ist abhängig von der Bandbreite
Darüber hinaus können verschiedene Kerne verwendet werden. Die Rust Implementierung verwendet als Kern weiterhin die Dichtefunktion der Gaußschen Normalverteilung.
Die einzigen verfügbaren Parameter bei Verwendung von Naive Bayes sind also die verwendete Dichtefunktion und die Parameter dieser Dichtefunktion.
Getestet wurden:
- Gaussian: Dichtefunktion der Gaußschen Normalverteilung
- KDE-ROT: Kernel Density Estimation mit der Rule Of Thumb (siehe oben)
- KDE (sonstige): Kernel Density Estimation mit fester Bandbreite (z.B. 10; 2; 1; 0.1; 0.05; 0.0001)
Gaussian und KDE-ROT lieferten die besten Ergebnisse (siehe 5.), während verschiedene feste Bandbreiten zu schlechteren Ergebnissen führten.
Mit einer Bandbreite von 10 (etwa der gesamte IQR/Interquartilsabstand) wurden 100% aller Testdaten als "Good" klassifiziert, während Bandbreiten zwischen im Bereich von 0,X nahe an die Wert der ROT gekommen sind.
Bei Verwendung gleicher Trainings- und Testdaten konnte mit einer Bandbreite von 0,0001 jeder Datensatz korrekt
klassifiziert werden (Overfitting).
Insgesamt ist der am besten geeignete Parameter beim Naive Bayes Algorithmus lediglich die Verwendung der Dichtefunktion der Gaußschen Normalverteilung gewesen. Weitere Parameter z.B. bei der KDE haben zu keiner Verbesserung geführt.
siehe 2.
| Laufzeit (Rust, Gaussian) | Wert |
|---|---|
| Training | 1064 us (1.0 ms) |
| Testing | 2580 us (2.6 ms) |
| Laufzeit (Python, Gaussian) | Wert |
|---|---|
| Training | 3713 us (3.7 ms) |
| Testing | 874 us (0.9 ms) |
| Laufzeit (Rust, KDE-ROT) | Wert |
|---|---|
| Training | 5185 us (5.2 ms) |
| Testing | 442 714 us (442.7 ms) |
Die Laufzeit für Training (6400 Datensätze) und Testing (Vorhersage der übrigen 1600 Datensätze) liegt bei beiden Implementierungen (Rust und Python) unter Verwendung der Gaußschen Dichtefunktion bei 3-4 ms. Die lange Laufzeit beim Testing unter Verwendung der Kernel Density Estimation mit der Rule Of Thumb zur Bestimmung der Bandbreite ist dadurch bedingt, dass die Dichtefunktion an einem Punkt in Abhängigkeit von den Trainings- und Testdaten berechnet werden muss. Dieser Schritt (lineare Komplexität) kann nicht bereits beim Training passieren.
| Ergebnis (Gaussian) | Wert |
|---|---|
| True Positives | 735 |
| True Negatives | 682 |
| False Positives | 117 |
| False Negatives | 66 |
| Sum | 1600 |
| Ergebnis (KDE-ROT) | Wert |
|---|---|
| True Positives | 699 |
| True Negatives | 699 |
| False Positives | 100 |
| False Negatives | 102 |
| Sum | 1600 |
Die Ergebnisse der Rust und Python Implementierung (Gaussian) sind identisch, wobei die Positives der Klasse Good entsprechen.
Die Variante Gaussian hat im Vergleich zu KDE-ROT insgesamt mehr Datensätze korrekt klassifiziert.
Bei der Variante KDE-ROT sind jedoch die fehlerhaften Klassifizierungen gleichmäßiger auf beide Klassen
verteilt.
| Fehlermetrik (Gaussian) | Wert |
|---|---|
| Accuracy | 0.885625 |
| Error | 0.114375 |
| Precision | 0.8626760563380281 |
| Recall | 0.9176029962546817 |
| F1-Score | 0.8892921960072595 |
| Fehlermetrik (KDE-ROT) | Wert |
|---|---|
| Accuracy | 0.874375 |
| Error | 0.125625 |
| Precision | 0.8748435544430538 |
| Recall | 0.8726591760299626 |
| F1-Score | 0.87375 |
Die Fehlermetriken (Gaussian) unterscheiden sich zwischen den beiden Implementierung nur minimal aufgrund von sehr
kleinen Rundungsfehlern.
Die Variante Gaussian ist in jeder Fehlermetrik außer der Precision besser als KDE-ROT. Die Unterschiede liegen jedoch
im Bereich von einem Prozentpunkt (mit Ausnahme vom Recall).
Die Klassifizierung der Daten mithilfe des Naive Bayes Algorithmus hat insgesamt relativ gut funktioniert.
Die Recherche-Ergebnisse wurden zur Implementierung des Naive Bayes Algorithmus in Rust verwendet. Auf Basis dieser kann die Komplexität des Algorithmus beschrieben werden, wobei zwischen den verschiedenen Dichtefunktionen unterschieden werden muss.
Bei Verwendung der Dichtefunktion der Gaußschen Normalverteilung müssen beim Training lediglich folgende Werte berechnet werden:
- Relative Häufigkeit einer Klasse
- Erwartungswerte (Durchschnitt) eines Features/Merkmals über alle Datensätze einer Klasse
- Stichprobenstandardabweichung eines Features/Merkmals über alle Datensätze einer Klasse
Zur Berechnung eines dieser Werte wird jeder Datensatz genau einmal betrachtet (2 Schleifen über alle Datensätze und Features).
Diese Berechnungen haben also eine lineare Laufzeit-Komplexität:
Die Speicher-Komplexität ist ebenfalls linear:
Für das Testing gilt:
Laufzeit-Komplexität:
Die Speicher-Komplexität ist nun:
Bei der Kernel Density Estimation muss nun nicht mehr der Erwartungswert berechnet werden. Stattdessen ist die
Bestimmung des IQR/Interquartilsabstands notwendig.
Die Bestimmung des IQR für ein Feature einer Klasse hat eine Laufzeit-Komplexität von
Das Training hat damit eine Laufzeit-Komplexität von
Die Speicher-Komplexität bleibt im Vergleich zu Gaussian unverändert, da der verwendete Sortieralgorithmus eine lineare Speicher-Komplexität hat.
Das Testing ist jedoch wesentlich komplexer, da nun jedes Feature eines zu klassifizierenden Datensatzes zusammen mit jedem Feature jedes Datensatzes jeder Klasse der Trainingsdaten betrachtet werden muss.
Die Laufzeit-Komplexität beträgt dann
Die Speicher-Komplexität bleibt unverändert zu Gaussian.
Aufgrund der hohen Laufzeit-Komplexität und größtenteils schlechteren Ergebnisse der Kernel Density Estimation eignet sich die Variante Gaussian beim gegebenen Datensatz besser.
Der verwendete Machine Learning Algorithmus ist die Support Vector Machine.
Siehe python main.py SVM.
Hinweis: Trotz Verwendung des StratifiedShuffleSplit und des Seeds 42 erzeugen scikit-learn (Python) und KNIME einen unterschiedlichen Train/Test-Split.
Für die Implementierung in Python wurde der Split der Python-Implementierung verwendet.
Die State Vector Machine in der Bibliothek scikit ist in der Klasse SVC (State Vector Classification) enthalten.
Scikit liefert für diese 15 Hyperparameter, von denen die folgenden für die Implementierung verwendet wurden:
- kernel: Spezifiziert, welcher Kernel zur Bestimmung der Entscheidungsgrenze verwendet wird. Wir haben uns für die rbf (radial basis function) entschieden, da diese in unseren Tests allgemein am besten abgeschnitten hat. Sie versucht eine Grenze der zu klassifizierenden Daten anhand der folgenden Funktion zu bestimmen: $$ K(x_i, x_j) = \exp\left(-\gamma, ||x_i - x_j||^2\right)$$
- C: Ein Regularisierungsparameter welcher eine Toleranz gegenüber Ausreißern erzeugt. Hohe C-Werte können zu Overfitting führen, während kleine Werte zu mehr Fehler, aber dafür zu einer glatteren Trennlinie führt. C legt also fest, wie stark Fehlklassifikation bestraft wird. 1.0 stellt einen soliden C-Wert dar, weshalb wir diesen verwendet haben.
-
gamma: Bestimmt den Radius des Einflusses von einzelnen Datenpunkten auf die Grenzfunktion. Große Gamma-Werte führen zu
sehr lokalen Einflüssen und somit komplexen Funktionen und somit zu Overfitting. Kleine Gamma-Werte resultieren in Underfitting.
Konfiguriert haben wir Gamma auf
scale, was den Startwert von Gamma aus der folgenden Formel berechnet:
$$\gamma = \frac{1}{n_{\text{features}} \cdot \mathrm{Var}(X)}$$ -
random_state: Das Äquivalent des
random_seed. Auf 42 festgelegt. -
probability: Auf
truegesetzt, damit derrandom_statenicht ignoriert wird.
siehe 2.
Laufzeit (SVC, mit probability=True) |
Wert |
|---|---|
| Training | 685407 us (685 ms) |
| Testing | 50311 us (50 ms) |
Laufzeit (SVC, mit probability=False) |
Wert |
|---|---|
| Training | 107698 us (107 ms) |
| Testing | 46253 us (46 ms) |
Das Training auf der Support Vector Machine mit probability=True dauert 685ms. Hier ist wichtig zu erwähnen, dass enablen des Parameters
probability für eine verlängerte Trainingszeit sorgt, da dann die sog. 5-fold cross-validation durchgeführt wird.
Ohne diesen Wert dauert das training nur 107ms. Die Testingzeit und die resultierenden Ergebnisse bleiben unverändert.
Das Testing benötigt 46-50ms.
| Ergebnis | Wert |
|---|---|
| True Positives | 781 |
| True Negatives | 790 |
| False Positives | 9 |
| False Negatives | 20 |
| Sum | 1600 |
| Fehlermetrik | Wert |
|---|---|
| Accuracy | 0.98188 |
| Error | 0.01812 |
| Precision | 0.98861 |
| Recall | 0.97503 |
| F1-Score | 0.98177 |
Insgesamt hat das Training mit der Support Vector Machine gut funktioniert. Im Vergleich zum Naive Bayes lässt aber vor allem die Trainings und Testing Zeit zu wünschen übrig.
Die Komplexität des Algorithmus unterscheidet sich zwischen den verschiedenen Kernel-Funktionen. Im Folgenden wird hauptsächlich auf den verwendeten RBF-Kernel eingegangen.
Im Rahmen des Trainingsprozesses wird eine Kernelmatrix erstellt, in der die Ähnlichkeit zwischen allen Trainingsdaten berechnet wird. Jede Eintrag dieser Matrix beschreibt die Distanz zweier Datenpunkte im Raum unter Verwendung der bereits angesprochenen RBF-Funktion: $$ K(x_i, x_j) = \exp\left(-\gamma, ||x_i - x_j||^2\right)$$
Traingsphase:
Für die Berechnung der Kernelmatrix müssen alle möglichen Paare von Datensätze betrachtet werden. Dies führt zu einem quadratischem Aufwand hinsichtlich der Traingsdaten
Anschließend muss ein quadratisches Optimierungsproblem gelöst werden, um die optimalen Gewichte zu bestimmen. Die theoretische Komplexität liegt dasfür bei
Die Speicher-Komplexität wird durch die Kernelmatrix dominiert und beträgt
Testphase:
Bei der Klassifikation neuer Datenpunkte wird jeder Testdatensatz mit allen Support-Vektoren
Neben dem RBF-Kernel existieren natürlich noch weitere Funktionen, wie bspw. der lineare Kernel. Dabei kommt es nicht zu einer vollständigen Berechnung der Kernelmatrix und kann daher mit einer Laufzeit-Komplexität von


