Commit 53f2dfac authored by Konrad Völkel's avatar Konrad Völkel
Browse files

fix tex error

parent 070fddc5
Pipeline #92140 passed with stages
in 5 minutes and 18 seconds
%% Cell type:markdown id:78ed001f-41b1-46be-817d-4234fc0fa95d tags:
 
# Support-Vektor Maschinen (SVMs)
 
**Support vector machines** sind ein supervised learning Verfahren,
bei dem für Punkte in einem Vektorraum, die in zwei Klassen geteilt sind, eine diese Klassen möglichst gut separierende Hyperebene berechnet wird.
Die Hyperebene wird dual durch den Normalenvektor auf der Hyperebene beschrieben.
Die Projektion auf den Normalenvektor liefert eine (vorzeichenbehaftete) Entscheidungsfunktion zur Einteilung in die Klassen (negativ= erste Klasse, positiv=zweite Klasse), die mit der Entscheidungsfunktion `auf welcher Seite der Hyperebene` übereinstimmt.
 
Bei der SVM wird im Gegensatz zur LDA nicht der vollständige Datensatz berücksichtigt, sondern **support Vektoren** nahe der separierenden Hyperebene. Das präzisieren wir nach einem Beispiel.
 
## Lineare SVM in der Ebene
 
```{admonition} Beispiel
In der Ebene ist eine separierende Hyperebene genau eine Gerade, der Normalenvektor ein Vektor, der senkrecht auf der Geraden steht.
```
 
%% Cell type:code id:25bbf588-34de-444b-a8ae-5e2162f38a40 tags:
 
``` python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
np.random.seed(512)
 
# generate sample:
x, y = make_blobs(n_samples=300, n_features=2,
centers=2, center_box=(-4,4))
x = StandardScaler().fit_transform(x)
 
# guess a separating hyperplane:
b = 0
m = -2
xs = np.linspace(-2.5, 2.5, 100)
ys = xs*m + b
 
# plot:
fig, ax = plt.subplots(figsize=(8,3))
ax.scatter(x[:,0], x[:,1], c=y)
ax.plot(xs, ys, color='red')
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:f6b31a5f-aa3f-4d2a-928c-dda0dab0bfd0 tags:
 
In der Ebene ist ein Normalenvektor $w$ zu einer Geraden $y = mx + b$ gegeben durch die Gleichung $w^T v = 0$, wobei $v$ ein Richtungsvektor der Geraden ist, also hier $v = (1, m)$.
Damit haben wir die Formel $w_0 + w_1 m = 0$. Legen wir $w_1=1$ fest (die Länge ist schließlich nicht eindeutig bestimmt),
so können wir $w_0 = - m$ berechnen.
 
%% Cell type:code id:cd72bea5-7c97-42fb-ac5c-0b89d513e5ad tags:
 
``` python
# compute vector normal to separating hyperplane:
w = np.array([ 1, -m ]) # (1, 2)
 
# plot:
fig, ax = plt.subplots(figsize=(8,8))
ax.scatter(x[:,0], x[:,1], c=y)
ax.plot(xs, ys, color='red')
ax.arrow(0, 0, w[0], w[1],
shape='full', width=0.05)
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:622194cd-7d3b-4b21-8161-4640c6e25ffa tags:
 
So eine separierende Hyperebene bzw. den Normalenvektor können wir nutzen, um eine Klasseneinteilung vorzunehmen:
 
%% Cell type:code id:4ca0680c-e777-46fd-8b74-c5c709306c4e tags:
 
``` python
def prediction(x, normal_vector):
return np.linalg.norm(x - normal_vector, axis=1)
 
fig, ax = plt.subplots(figsize=(7,7))
ax.scatter(x[:,0], x[:,1], c=prediction(x, w))
ax.plot(xs, ys, color='red')
ax.arrow(0, 0, w[0], w[1],
shape='full', width=0.05)
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:24351d8e-2afe-4416-9426-05b60c490622 tags:
 
Wenn wir nun eine optimale trennende Gerade finden wollen, können wir einerseits zur LDA greifen (sie liefert uns den Normalenvektor), andererseits aber auch einfach die Gerade verschieben und rotieren und dabei beobachten, wie sich die Anzahl an fehlklassifizierten Punkten ändert. Es kommt dann nur auf die Punkte nahe der Geraden an.
 
```{admonition} Beispiel
Wenn man nur $4$ Punkte betrachtet, lassen sich viele Geraden finden, die auf diesen $4$ Punkten gleich gut sind.
Um diejenige Gerade auszuwählen, die eine gute Verallgemeinerung erlaubt (also nicht overfittet), können wir den Abstand zu den Punkten nahe der Gerade maximieren. So liegt die Gerade dann in der 'Mitte' zwischen den Punkten, die nah sind.
```
 
%% Cell type:code id:bd1a0bc3-740e-46b6-9bf7-d04782eed817 tags:
 
``` python
from sklearn.model_selection import train_test_split
np.random.seed(12345)
x_train, x_test, y_train, y_test = train_test_split(x, y, train_size=4)
 
# guess a separating hyperplane:
b = 0
m = -2
xs = np.linspace(-1.2, 1.2, 100)
ys = xs*m + b
w = np.array([ 1, -m ]) # (1, 2)
 
fig, ax = plt.subplots(figsize=(7,7))
ax.scatter(x_train[:,0], x_train[:,1], c=y_train)
ax.scatter(x_test[:,0], x_test[:,1], c=prediction(x_test, w),
alpha=.1, marker='.')
ax.plot(xs, ys, color='red')
for point in x_train:
# lotfusspunkt:
f0 = (point[0] + m*(point[1] - b)) / (1 + m**2)
f1 = b + (m*(point[0] + m*(point[1] - b)) / (1 + m**2))
ax.plot([point[0], f0], [point[1], f1], color='green')
plt.show()
```
 
%% Output
 
 
%% Cell type:markdown id:f57be56a-fc5b-489f-b864-522c1b83a369 tags:
 
Jetzt können wir die rote Gerade rotieren und translieren, bis wir die Summe der Längen der grünen Strecken maximiert haben (unter Beibehaltung der Klassifikation).
 
%% Cell type:markdown id:9af65636-201c-49c1-946b-2c36ab67f825 tags:
 
Nochmal explizit: wir klassifizieren den Punkt $x_i$ nach der Formel
 
$$
\texttt{sign}(w^T x_i + b)
$$
 
in $-1$ und $1$ (und mit $+1$ und dann $/2$ landen wir zwischen $0$ und $1$).
 
%% Cell type:markdown id:7e19a39c-0b00-456d-994f-d7db020f6185 tags:
 
### Support-Vektoren
 
Wir können die rote Gerade (allgemeiner: die separierende Hyperebene) nun entlang des Normalenvektors parallel verschieben, ein Stückchen in jede Richtung, ohne dass sich die Klassifikation ändert. Den so maximal erreichbaren Abstand zwischen zwei parallelen separierenden Hyperebenen nennen wir den **Abstand** der Klassifikation im Sinne der Support-Vektor Klassifikation.
 
Zwangsläufig liegen auf diesen zwei extremen Hyperebenen Punkte des betrachteten Samples - sie heißen **Support-Vektoren** der SVM. Allgemein, wenn keine perfekte Trennung der beiden Klassen möglich ist, heißen die Punkte zwischen den zwei extremen Hyperebenen ebenfalls Support-Vektoren.
 
%% Cell type:markdown id:454c3409-ec5c-49e0-bb13-16cf74092b25 tags:
 
### SVM über das duale Problem
 
Weil wir im Allgemeinen nicht davon ausgehen können, dass wir die Punkte perfekt separieren können, können wir auch nicht davon ausgehen, dass wir stets $y_i (w^T x_i + b) \geq 1$ erreichen können. Stattdessen erlauben wir für jedes $x_i$ einen Abweichungsterm $\zeta_i \geq 0$ sodass $y_i (w^T x_i + b) \geq 1 - \zeta_i$ ist.
 
Das Optimierungsverfahren besteht nun darin, sowohl $w$ zu minimieren als auch die Summe der $\zeta_i$. Das Verhältnis zwischen beiden wird festgelegt durch eine Konstante $C$, ähnlich dem Inversen von $\alpha$ bei der Ridge-Regularisierung:
 
$$
\min_{w,b,\zeta} \frac{1}{2} w^T w + C\sum_{i=1}^N \zeta_i
 
\text{sodass } y_i (w^T x_i + b) \geq 1 - \zeta_i \text{ für alle } i=1,\dots,N
$$
 
Dieses Problem ist so noch nicht leicht zu lösen. Man kann sich aber etwas Optimierungstheorie zunutze machen, die einem sagt, dass man alternativ das *duale Problem* lösen kann:
 
$$
\min \frac{1}{2} \alpha^T Q \alpha - e^T\alpha
 
\text{sodass } y^T \alpha = 0
$$
 
dabei ist $e$ der Vektor, der nur $1$en enthält und $Q$ ist eine $N \times N$-Matrix mit Einträgen $Q_{ij} = y_iy_j \langle x_i, x_j\rangle$.
Die Terme $\alpha_i$ heißen duale Koeffizienten, und sie liegen zwischen $0$ und $C$.
 
In dieser Formulierung wird ein Punkt $x_i$ klassifiziert nach
 
$$
\sum_{j=1}^N y_j \alpha_j \langle x_i, x_j\rangle + b
$$
 
%% Cell type:markdown id:f39efe7e-e832-4539-ab52-0a1989167ef2 tags:
 
### SVM mit Basisfunktionserweiterung und Kernel
 
Wir können in die SVM-Berechnung von vornherein anstelle von $x$ auch eine Basisfunktionserweiterung $\phi$ verwenden, etwa $(1,x)$ oder $(1,x,x^2)$.
In der dualen Formulierung sehen wir, dass wir dann $\langle \phi(x_i), \phi(x_j) \rangle$ berechnen müssen - stattdessen können wir auch einen Kernel $\kappa$ verwenden.
 
Die Kombination SVM + Kernel ist so mächtig und üblich, dass man mit 'SVM' eigentlich meist eine SVM mit einem potentiell nichtlinearen Kernel meint.
 
Die Entscheidungsfunktion, wie ein Punkt $x_i$ klassifiziert wird, ist
 
$$
\sum_{j=1}^N y_j \alpha_j \kappa \left( x_i, x_j \right) + b
$$
 
Was zuvor noch eine Gerade war, gerät dann natürlich zu einer anderen Form, die im Feature-Raum zwar eine Gerade ist, aber im ursprünglichen Raum nichtlinear sein darf.
 
%% Cell type:markdown id:ac97f391-dc46-4e67-a61f-b8bc0e1ecd80 tags:
 
### SVM mit Scikit-learn
 
Eine SVM zur Klassifikation zu verwenden, nennt man **Support vector classification** und es gibt dafür zwei Methoden in Scikit-learn - $\texttt{LinearSVC}$ und $\texttt{SVC}$.
 
Die Methode $\texttt{SVC}$ (bzw., um genau zu sein, $\texttt{SVC().fit()}$ und $\texttt{.predict()}$) ist allgemeiner als $\texttt{LinearSVC}$, denn mit $\texttt{SVC(kernel='linear')}$ erreicht man das gleiche wie mit $\texttt{LinearSVC()}$,
aber $\texttt{LinearSVC()}$ ist potentiell schneller.
 
Mit $\texttt{SVC}$ lässt sich direkt $\texttt{kernel='rbf'}$ oder natürlich $\texttt{kernel='precomputed'}$ oder auch $\texttt{kernel=callable}$ verwenden.
 
Ein $\texttt{SVC.fit()}$ hat die Attribute $\texttt{dual_coef_}$, die die Werte von $y_i\alpha_i$ enthalten, $\texttt{support_vectors_}$ und $b=\texttt{intercept_}$.
Ein $\texttt{SVC.fit()}$ hat die Attribute $\texttt{dual\_coef\_}$, die die Werte von $y_i\alpha_i$ enthalten, $\texttt{support\_vectors\_}$ und $b=\texttt{intercept\_}$.
 
%% Cell type:code id:bfe26c1f-e4f6-4a3d-b794-3d6d8291faea tags:
 
``` python
from sklearn.svm import LinearSVC
 
svc = LinearSVC().fit(x_train, y_train)
y_pred = svc.predict(x_test)
x_wrong = x_test[y_test != y_pred]
print("Got %d classificaton mistakes" % len(x_wrong))
 
fig, ax = plt.subplots(figsize=(10,10))
ax.scatter(x_test[:,0], x_test[:,1], c=y_pred, marker="o")
ax.scatter(x_wrong[:,0], x_wrong[:,1], color='red', marker="x")
plt.show()
```
 
%% Output
 
Got 31 classificaton mistakes
 
 
%% Cell type:code id:c0185746-0626-4b1e-8c23-a02125e26885 tags:
 
``` python
from sklearn.svm import SVC
 
svc = SVC(kernel='poly', degree=5, coef0=22).fit(x_train, y_train)
y_pred = svc.predict(x_test)
x_wrong = x_test[y_test != y_pred]
print("Got %d classificaton mistakes" % len(x_wrong))
 
fig, ax = plt.subplots(figsize=(10,10))
ax.scatter(x_test[:,0], x_test[:,1], c=y_pred, marker="o")
ax.scatter(x_wrong[:,0], x_wrong[:,1], color='red', marker="x")
plt.show()
```
 
%% Output
 
Got 35 classificaton mistakes
 
 
%% Cell type:markdown id:283e9a74-8f6a-43ea-95ac-419659859885 tags:
 
### Support-Vektor Regression
 
Man kann SVM auch zur Regression verwenden - dazu projiziert man einfach auf den Normalenvektor der separierenden Hyperebene.
 
Achtung: aufgrund der Konstruktion hat die Hyperebene der SVM und damit auch die Regression keine direkte Interpretation durch Wahrscheinlichkeiten!
Sie ist sozusagen eine 'echte' Machine-Learning-Technik, keine Technik des statistischen Lernens.
 
%% Cell type:markdown id:988a58c0-e7e3-490c-8d70-e286fa950578 tags:
 
## Anwendung von SVM: MNIST
 
%% Cell type:code id:1685ba4b-e707-4690-b3bf-c0fcb9f41947 tags:
 
``` python
from sklearn.datasets import load_digits
 
x, y = load_digits(return_X_y=True)
x_train, x_test, y_train, y_test = train_test_split(
x, y, test_size=0.5, shuffle=False
)
 
svm = SVC(gamma=0.001).fit(x_train, y_train)
y_pred = svm.predict(x_test)
```
 
%% Cell type:markdown id:91f4da43-bc24-42f1-a6fa-944972c715a2 tags:
 
Wie gut unsere Vorhersage ist, kann Scikit-learn für uns zusammenfassen:
 
%% Cell type:code id:31bedfcb-e5bf-42bb-9892-061c9bb2156a tags:
 
``` python
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
```
 
%% Output
 
precision recall f1-score support
0 1.00 0.99 0.99 88
1 0.99 0.97 0.98 91
2 0.99 0.99 0.99 86
3 0.98 0.87 0.92 91
4 0.99 0.96 0.97 92
5 0.95 0.97 0.96 91
6 0.99 0.99 0.99 91
7 0.96 0.99 0.97 89
8 0.94 1.00 0.97 88
9 0.93 0.98 0.95 92
accuracy 0.97 899
macro avg 0.97 0.97 0.97 899
weighted avg 0.97 0.97 0.97 899
 
%% Cell type:markdown id:b982664d-ae24-4fcd-8dd2-ef683f9506c5 tags:
 
*Precision* von $d$ ist der Anteil an korrekt als $d$ klassifizierten an allen als $d$ klassifizierten Datenpunkten.
 
*Recall* von $d$ ist der Anteil an korrekt als $d$ klassifizierten an allen Datenpunkten, die eigentlich in Klasse $d$ gehören.
 
Der *F1-Score* von $d$ ist das harmonische Mittel aus Precision und Recall, es liegt zwischen $0$ und $1$ und der beste Wert ist $1$.
 
Der *Support* von $d$ ist die Anzahl aller tatsächlich in Klasse $d$ liegenden Datenpunkte.
 
%% Cell type:code id:f0b3e803-88f7-4bb0-9d5d-748367c37e05 tags:
 
``` python
from sklearn.decomposition import PCA
 
pc2_test = PCA(2).fit_transform(x_test)
pc2_wrong = pc2_test[y_test != y_pred]
 
_, ax = plt.subplots(figsize=(10,10))
for digit in range(10):
pc2_test_singledigit = pc2_test[y_test == digit]
y_pred_singledigit = y_pred[y_test == digit]
ax.scatter(pc2_test_singledigit[:,0], pc2_test_singledigit[:,1],
c=y_pred_singledigit, vmin=0, vmax=9,
marker="o", label=digit)
ax.scatter(pc2_wrong[:,0], pc2_wrong[:,1], color='red', marker="x")
ax.legend()
plt.show()
```
 
%% Output
 
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment