- Startseite
- Allgemeine Informationen zur Lehrveranstaltung
- Einfaches Python Setup und Wichtige Informationen
- 0. Python Einführung und Checkup
- 1. Einführung und einfache Algorithmen
- 2. Differenzieren und Integrieren
- 3. Vektoren, Matrizen und Vektorisierung in Python
- 4. Datenanalyse bzw. Datenauswertung
- 5. Grundlagen der Optimierung und Gradient Descent
- 6. Stochastische Optimierung und Genetische Algorithmen
- 7. Monte-Carlo-Methoden – Simulation und Integration
- 8. Monte-Carlo-Methoden, Teil 2 – Monte-Carlo-Integration, Teil 2 und Random Walk
- 9. Unsupervised Machine Learning: Clustering von Daten
- 10. Supervised Machine Learning: Grundlagen
- 11. Einführung in künstliche neuronale Netzwerke
Die Jupyter-Notebooks zur Lehrveranstaltung finden Sie im zugehörigen GitHub-Repository.
3. Vektoren, Matrizen und Vektorisierung in Python¶
In dieser Einheit werden wir uns einerseits einige Werkzeuge für das Rechnen mit Vektoren und Matrizen in Python ansehen. Andererseits möchte ich Ihnen das Prinzip der Vektorisierung klar machen (und nahelegen).
Hier zunächst die Imports für heute:
%matplotlib inline
import matplotlib.pyplot as plt # für plotting, wie gewohnt
from matplotlib import image # zum Einlesen und arbeiten mit Bilddateien
import numpy as np # für numerische Aktionen mit Arrays, wie gewohnt
from tqdm import tqdm # zum Erzeugen eines Fortschrittsbalkens
import os # Funktionen zum OS
# Ein Vektor ist in Python im Prinzip ein 1D-Array
a_vector = np.array([2, 3, -1])
# Zwei Vektoren lassen sich recht einfach addieren und subtrahieren
another_vector = np.array([4, -2, 1])
print("Die Summe: ", a_vector + another_vector)
print("Die Differenz: ", a_vector - another_vector)
print("Das punktweise Produkt: ", a_vector * another_vector)
print("Der punktweise Quotient: ", a_vector / another_vector)
print("Punktweises Potenzieren: ", a_vector.astype(float) ** another_vector)
Die Summe: [6 1 0] Die Differenz: [-2 5 -2] Das punktweise Produkt: [ 8 -6 -1] Der punktweise Quotient: [ 0.5 -1.5 -1. ] Punktweises Potenzieren: [16. 0.11111111 -1. ]
Was ist aber mit dem Skalar-Produkt $\vec{a}\cdot\vec{b}$ oder dem Kreuz-Produkt $\vec{a}\times\vec{b}$? Eine Möglichkeit ist es, NumPy Funktionen dafür zu verwenden und die entsprechenden Ausdrücke zu basteln. Oder, man bedient sich fertiger Funktionen, die ebenfalls in NumPy zu finden sind.
# bastel-Variante für das Skalarprodukt
print(np.sum(a_vector * another_vector))
# built-in-Variante für das Skalarprodukt
print(np.dot(a_vector, another_vector))
# built-in-Variante für das Kreuzprodukt
print(np.cross(a_vector, another_vector))
1 1 [ 1 -6 -16]
3.2 Matrizen und Lineare Algebra¶
Matrizen sind in Python im Prinzip 2-dimensionale Arrays. Man gibt einem NumPy-Array einen Zeilen- und einen Spalten-Index und kann damit dann verschiedene Dinge tun (das meiste davon geht natürlich auch schon in einer Dimension, ist aber in 2D anschaulicher), z.B.:
- Die Matrix transponieren
- Einen Teil der Matrix herausschneiden
- Elemente an einer bestimmten Position verändern, hinzufügen, löschen
- Matrizen miteinander (also auch mit Vektoren) multiplizieren
- Eigenschaften der Matrix berechnen, wie z.B. die Determinante, den Rang, etc.
- Operationen aus der Linearen Algebra ausführen, wie z.B. Eigenwerte bestimmen
Hier eine Auswahl der Dinge auf dieser Liste:
# definiere eine Matrix
a_matrix = np.array([[2, 3, 5, -3], [1, 3, -1, 4], [1, 2, -3, 2], [3, 4, -2, 1]])
a_matrix
array([[ 2, 3, 5, -3], [ 1, 3, -1, 4], [ 1, 2, -3, 2], [ 3, 4, -2, 1]])
# verschiedene Slices/Teile
print("Erste Zeile: ", a_matrix[0, :])
print("Erste Spalte: ", a_matrix[:, 0])
print("Letzte Zeile: ", a_matrix[-1, :])
print("Letzte Spalte: ", a_matrix[:, -1])
print("Element links oben: ", a_matrix[0, 0])
print("Element an bestimmter Position: ", a_matrix[2, 3])
print("Elemente im Viereck innen: \n", a_matrix[1:3, 1:3])
print("Jedes 2. Element der 2. Zeile: ", a_matrix[1, ::2])
print("Dritte Spalte, von unten nach oben: ", a_matrix[::-1, 2])
Erste Zeile: [ 2 3 5 -3] Erste Spalte: [2 1 1 3] Letzte Zeile: [ 3 4 -2 1] Letzte Spalte: [-3 4 2 1] Element links oben: 2 Element an bestimmter Position: 2 Elemente im Viereck innen: [[ 3 -1] [ 2 -3]] Jedes 2. Element der 2. Zeile: [ 1 -1] Dritte Spalte, von unten nach oben: [-2 -3 -1 5]
# transponierte Matrix
np.transpose(a_matrix)
array([[ 2, 1, 1, 3], [ 3, 3, 2, 4], [ 5, -1, -3, -2], [-3, 4, 2, 1]])
# Elemente verändern
a_changed_matrix = a_matrix
a_changed_matrix[0, 1] = -4
second_row = a_changed_matrix[1]
print("Matrix geändert auf \n", a_changed_matrix)
print("Die zweite Zeile: ", second_row)
# ein Element an bestimmter Stelle in bestimmter Richtung herauslöschen
a_changed_matrix = np.delete(a_changed_matrix, 1, axis=0)
print("Zweite Zeile wurde gelöscht: \n", a_changed_matrix)
# Die gelöschte Zeile wieder einfügen
a_changed_matrix = np.insert(a_changed_matrix, 1, second_row, axis=0)
print("Zweite Zeile wieder eingefügt: \n", a_changed_matrix)
# eine Zeile anhängen
a_changed_matrix = np.append(a_changed_matrix, [second_row], axis=0)
print("Zweite Zeile nochmal angehängt: \n", a_changed_matrix)
# zwei Arrays kombinieren
a_changed_matrix = np.concatenate((a_changed_matrix, a_changed_matrix), axis=1) # versuche auch axis=0
print("Die veränderte Matrix, kombiniert mit sich selbst: \n", a_changed_matrix)
# und, wichtig: Die Dimensionen der Matrix verändern (also das Array verformen)
a_changed_matrix = np.reshape(a_changed_matrix, (2, -1))
print("zwei Zeilen und wie viele Spalten? \n", a_changed_matrix)
Matrix geändert auf [[ 2 -4 5 -3] [ 1 3 -1 4] [ 1 2 -3 2] [ 3 4 -2 1]] Die zweite Zeile: [ 1 3 -1 4] Zweite Zeile wurde gelöscht: [[ 2 -4 5 -3] [ 1 2 -3 2] [ 3 4 -2 1]] Zweite Zeile wieder eingefügt: [[ 2 -4 5 -3] [ 1 3 -1 4] [ 1 2 -3 2] [ 3 4 -2 1]] Zweite Zeile nochmal angehängt: [[ 2 -4 5 -3] [ 1 3 -1 4] [ 1 2 -3 2] [ 3 4 -2 1] [ 1 3 -1 4]] Die veränderte Matrix, kombiniert mit sich selbst: [[ 2 -4 5 -3 2 -4 5 -3] [ 1 3 -1 4 1 3 -1 4] [ 1 2 -3 2 1 2 -3 2] [ 3 4 -2 1 3 4 -2 1] [ 1 3 -1 4 1 3 -1 4]] zwei Zeilen und wie viele Spalten? [[ 2 -4 5 -3 2 -4 5 -3 1 3 -1 4 1 3 -1 4 1 2 -3 2] [ 1 2 -3 2 3 4 -2 1 3 4 -2 1 1 3 -1 4 1 3 -1 4]]
# zwei Matrizen miteinander multiplizieren
a_matrix_squared = np.matmul(a_matrix, a_matrix)
a_matrix_squared
array([[ -4, -22, 5, -15], [ 16, 19, -3, 11], [ 7, 4, 8, 1], [ 11, 0, 15, 4]])
# Ein paar Dinge aus der Linearen Algebra
print("Rang der Matrix ", np.linalg.matrix_rank(a_matrix))
print("Determinante der Matrix ", np.linalg.det(a_matrix))
print("Eigensystem der Matrix: ")
print("Eigenwerte der Matrix: ", np.linalg.eig(a_matrix)[0])
print("Eigenvektoren der Matrix: \n", np.linalg.eig(a_matrix)[1])
Rang der Matrix 4 Determinante der Matrix 126.0 Eigensystem der Matrix: Eigenwerte der Matrix: [ 3.82292406+2.38876617j 3.82292406-2.38876617j -2.32292406+0.89695077j -2.32292406-0.89695077j] Eigenvektoren der Matrix: [[-0.71322195+0.j -0.71322195-0.j 0.10244504-0.22757172j 0.10244504+0.22757172j] [ 0.40254467+0.3848397j 0.40254467-0.3848397j 0.47509046+0.19209634j 0.47509046-0.19209634j] [ 0.09504103+0.18690531j 0.09504103-0.18690531j -0.10378635+0.36880975j -0.10378635-0.36880975j] [ 0.05505865+0.36629606j 0.05505865-0.36629606j -0.7268507 +0.j -0.7268507 -0.j ]]
3.3 Beispiel: Lineare Regression¶
Um auch etwas konkret Sinnvolles mit Matrizen zu machen, greifen wir kurz auf die Optimierungsmethode der linearen Regression vor. Keine Sorge, wir sehen uns das später noch im Detail an. Einstweilen hier einmal nur der letzte Schritt der Anwendung.
Zunächst aber die wichtigsten Punkte: Die Lineare Regression leistet folgendes:
- Man hat einen Datensatz, bei dem es eine bis mehrere (insgesamt $N$) unabhängige Variablen $x_i$ und eine abhängige Variable $y$ gibt.
- Man möchte die Daten beschreiben, und zwar mit einer Funktion $f(x_i)$.
- Bei der Linearen Regression setzt man diese Funktion linear an, also $f(x_i)=\sum_{i=0}^N a_i x_i$
- Nimmt man dabei $x_0=1$ an, dann hat man den konstanten Term $a_0$ im Fit mit dabei.
- Nun hat man einen Datensatz von $M$ Datenvektoren $\vec{x}^{(j)}$ zur Verfügung, also $j=1,\ldots,M$
- Die Koeffizienten $a_i$ bestimmt man so, dass die Summe der Quadrate der Differenzen zwischen den $y^{(j)}$ und den $f(x_i)^{(j)}$ minimal ist (least-mean-squares fit).
Das sieht auf den ersten Blick verwirrend aus, aber worum es sich hier handelt, ist im Allgemeinen ein überbestimmtes System von linearen Gleichungen. Wir werden es uns außerdem anschaulich und leicht machen und nehmen eine einzige unabhängige Variable $x_1=x$ und die Konstante über $x_0=1$ mit.
# generieren wir erst einmal Daten
x_values = np.linspace(0, 10, 200)
fake_data = 0.5 * x_values + np.random.normal(size=len(x_values))
fig = plt.figure()
plt.scatter(x_values, fake_data, c="r", marker="x")
plt.show()
Hierfür sollen wir nun den besten linearen Fit finden. Dafür gibt es zwar auch iterative Algorithmen wie gradient descent, aber wir werden hier eine geschlossene Formel für die Lineare Regression verwenden.
Dabei schreibt man den Parameter-Vektor der $a_i$ als $\vec{a}$, den Vektor der Funktionswerte $y^{(j)}$ als $\vec{y}$ und schließlich die $x$ Werte als Matrix $X$. Warum als Matrix? Das kommt daher, dass es einerseits $N$ unabhängige Variablen und andererseits $M$ Datenpunkte gibt, d.h. $X=x_i^{(j)}$. Konkret ist dabei in $X$ das $i$ der Zeilen- und $j$ der Spalten-Index.
Dann lautet die Lösung für die Koeffizienten folgendermaßen: $$\vec{a}=(X^TX)^{-1}\,X^T \vec{y}$$ Und das setzen wir nun im Code um, und plotten dann die entsprechende Gerade zu den Daten dazu:
# Konstuiere y und X
y_vec = fake_data
# für die Matrix X werden x-Werte und Einsen "nebeneinander" geschrieben
x_mat = np.hstack((x_values.reshape((len(x_values),1)), np.ones((len(x_values),1))))
# berechne a nach der Formel. Das "@" ist eine Kurzschreibweise für Matrixmultiplikation
a_vec = np.matmul(np.linalg.pinv(np.transpose(x_mat) @ x_mat), np.transpose(x_mat) @ y_vec)
# die Lösung ist ein Vektor aus a_1 und a_0
print("Die fit-Koeffizienten: ", a_vec)
# Erzeuge die Fit-Gerade als Einsetzen der x-Werte in ein Polynom mit den Koeffizienten a_1 und a_0
reg_data = np.polyval(a_vec, x_values)
# neue Figur
fig = plt.figure()
# plotte wieder die Daten
plt.scatter(x_values, fake_data, c="r", marker="x")
# und dazu die Gerade
plt.plot(x_values, reg_data, "b", label="Linear fit")
# die Legende
plt.legend(loc=0)
plt.show()
Die fit-Koeffizienten: [0.48495158 0.03093417]
3.4 Höherdimensionale Arrays¶
Arrays können im Prinzip auch mehr als nur 2 Dimensionen haben. Man muss nicht unbedingt in die tiefen irgendwelcher Theorien eintauchen, um so etwas zu finden. In der Bildbearbeitung (und auch z.B. im maschinellen Lernen an Bildern) hat man mit einem 2-dimensionalen Array zu tun, das noch drei weitere “Kanäle” hat. Im Daten-Array für das Bild wird das als dritte Dimension angelegt, sodass ein Bild-Array im Allgemeinen die Dimensionen (Breite in Pixel)x(Höhe in Pixel)x3 hat.
Wir werden uns hier ein Bild hernehmen, um ein paar Tests und einfache Manipulationen auszuprobieren. Sie werden sehen, dass vieles recht einfach ist und Ihnen bereits ein gutes Gefühl für den Umgang mit höherdimensionalen Daten gibt.
# lade ein Bild über matplotlib-Bibliothek "image"
# os.path.join zum OS-unabhängigen aneinanderfügen von Dateipfaden
the_picture = image.imread(os.path.join('data', 'pinky.jpg'))
# schauen wir uns das Bild an
plt.figure()
# der Befehl für die Darstellung eines Bildes
plt.imshow(the_picture)
plt.show()
# eigentlich ist das Bild aber "nur" ein Array, wie oben beschrieben
the_picture
array([[[224, 220, 208], [224, 220, 208], [224, 220, 208], ..., [206, 211, 214], [205, 210, 213], [205, 210, 213]], [[226, 222, 210], [225, 221, 209], [225, 221, 209], ..., [207, 212, 215], [207, 212, 215], [207, 212, 215]], [[227, 223, 211], [227, 223, 211], [226, 222, 210], ..., [209, 214, 217], [209, 214, 217], [209, 214, 217]], ..., [[200, 189, 187], [200, 189, 187], [200, 189, 187], ..., [159, 150, 143], [157, 148, 141], [156, 147, 140]], [[197, 186, 184], [197, 186, 184], [197, 186, 184], ..., [161, 154, 146], [155, 148, 140], [148, 141, 133]], [[194, 183, 181], [194, 183, 181], [195, 184, 182], ..., [152, 145, 137], [154, 147, 139], [152, 145, 137]]], dtype=uint8)
# Die Dimension des Arrays:
np.shape(the_picture)
(1512, 2016, 3)
# Wir können diese Werte auch so normieren, dass sie zwischen 0 und 1 liegen:
the_picture_normalized = the_picture/256
# jetzt können wir mit "matshow" statt "imshow" weitermachen
plt.matshow(the_picture_normalized)
<matplotlib.image.AxesImage at 0x7fa960ee6be0>
Hier kommen nun einige einfache Manipulationen und Tests, die wir mit dem Bild machen können, indem wir einfach nur das Array verändern:
- Spiegelung
- Schwarz-Weiß
- Nur einen Farbkanal darstellen
- Aufhellen
- Einen Filter über das Bild laufen lassen
- Unschärfe
- Beliebiger Filter
# gespiegelt, rechts-links
# Slicen aller Indices der Dimension 1 in umgekehrter Reihenfolge
plt.matshow(the_picture_normalized[:,::-1,:])
plt.show()
# schwarz-weiß
# Hier brauchen wir eine Definition, wie man aus
# RGB-Werten (also den drei Farbkanälen) einen
# Grayscale-Kanal macht. Die einfachste Möglichkeit dafür
# ist ein arithmetisches Mittel:
the_picture_normalized_bw = np.mean(the_picture_normalized, axis=2)
# danach hat das Array eine Dimension verloren und sieht seltsam aus
print(np.shape(the_picture_normalized_bw))
# deshalb müssen wir spezifisch eine Colormap (cmap) angeben
# die richtige für grayscale ist "gray" (siehe unten), aber es gibt
# viele teils lustige Möglichgkeiten: Greys, spring, magma, PRGn, seismic, hsv, Set2, prism
plt.matshow(the_picture_normalized_bw, cmap="gray")
plt.show()
(1512, 2016)
# Nur ein Farbkanal
# Hier suchen wir uns also einen Kanal aus, z.B. R
the_picture_normalized_one_channel = the_picture_normalized[:,:,0]
# danach hat das Array wieder eine Dimension verloren
print(np.shape(the_picture_normalized_one_channel))
# deshalb müssen wir wieder eine Colormap (cmap) angeben
# die spezifischen hier sind "Reds", "Greens" und "Blues"
plt.matshow(the_picture_normalized_one_channel, cmap="Reds")
plt.show()
(1512, 2016)
# Alle drei Farbkanäle nebeneinander
plt.figure(figsize=(10, 5))
# Definiere alle nötigen Colormaps in einer Liste
colormaps = ["Reds", "Greens", "Blues"]
# Loop über den index der drei Farbkanäle
for ind_color in range(3):
ax = plt.subplot(1, 3, ind_color+1)
ax.matshow(the_picture_normalized[:,:,ind_color], cmap=colormaps[ind_color])
plt.show()
# Aufhellen
plt.figure(figsize=(10, 5))
# normales Bild zum Vergleich
ax = plt.subplot(1, 2, 1)
ax.matshow(the_picture_normalized)
# aufgehelltes Bild daneben
ax = plt.subplot(1, 2, 2)
# eine einfache Methode, Zahlen zwischen 0 und 1 größer zu machen
# ist, die Quadratwurzel zu ziehen
ax.matshow(np.sqrt(the_picture_normalized))
plt.show()