Lineare Algebra mit NumPy
NumPy installieren¶
In vielen mathematischen Anwendungen muss mit Vektoren und Matrizen gerechnet werden, beispielsweise bei der numerischen Berechnung von Integralen, Differentialgleichungen oder Problemen aus der Graphentheorie.
In diesem Kapitel beschäftigen wir uns daher mit einer Python-Bibliothek, die entsprechende Datentypen für Vektoren und Matrizen sowie effiziente Rechenoperationen darauf bereitstellt: NumPy.
Zunächst muss die NumPy-Bibliothek installiert werden. Bei Verwendung von Conda geschieht dies mit dem Konsolenbefehl
conda install numpyAnschließend muss die Bibliothek in unser Python-Skript eingebunden werden. Dies könnte – wie in Abschnitt Variablen und Datentypen – mit
from numpy import *geschehen. Diese Variante ist jedoch nicht empfehlenswert, da NumPy ebenfalls Funktionen wie sin, cos, sqrt usw. bereitstellt. Diese würden dann gleichnamige Funktionen aus anderen Bibliotheken (z. B. math) überschreiben.
Stattdessen verwenden wir üblicherweise folgende Schreibweise:
import numpy as npDer Zusatz as np gibt nur an, dass wir die Bibliothek in Zukunft unter dem kürzeren Namen np und nicht unter dem langen numpy ansprechen können.
Arbeiten mit Vektoren¶
Vektoren erzeugen¶
Ein Vektor bzw. NumPy-Array kann mit der Funktion np.array(...) von einem beliebigen iterierbaren Objekt (Liste, Tupel, ...), deren Elemente vom gleichen Typ sind, initialisiert werden:
a = np.array([1.,2.,3.])
b = np.array((6.,5.,4.))
print("a ist ein", type(a), "mit Wert", a)
print("b ist ein", type(b), "mit Wert", b)a ist ein <class 'numpy.ndarray'> mit Wert [1. 2. 3.]
b ist ein <class 'numpy.ndarray'> mit Wert [6. 5. 4.]
Die Klasse ndarray repräsentiert ein mehrdimensionales Array. In unserem Fall ein Vektor.
Die Anzahl der Dimensionen erhalten wir mit
a.ndim1Der Datentyp der gespeicherten Elemente ist
a.dtypedtype('float64')Die Form des Arrays (also die Größe jeder Dimension) erhalten wir mit
a.shape(3,)Zwei weitere nützliche Funktionen zur Erzeugung von NumPy-Arrays sind linspace und arange:
x = np.linspace(0,3,6) # Äquidistantes Punktgitter für [0,3] aus 6 Punkten
xarray([0. , 0.6, 1.2, 1.8, 2.4, 3. ])Die Funktion np.linspace(start, stop, num) erzeugt num gleichmäßig verteilte Punkte im Intervall [start, stop]. Beide Randpunkte werden dabei standardmäßig eingeschlossen.
x = np.arange(0, 3, 0.5) # Äquidistantes Punktgitter für [0,3) mit Inkrement 0.5
xarray([0. , 0.5, 1. , 1.5, 2. , 2.5])Die Funktion np.arange(start, stop, step) erzeugt eine Folge von Werten beginnend bei start mit Schrittweite step. Der Endwert stop wird dabei nicht mehr erreicht (analog zur Python-Funktion range).
Elementare Vektoroperationen¶
Ein ndarray ist – ähnlich wie eine Liste oder ein Tupel – ein iterierbares Objekt. Wir können daher mit einer for-Schleife über alle Elemente iterieren:
val = 0
for e in a:
val += e
print("Die Summe der Einträge von", a, "ist", val)Die Summe der Einträge von [1. 2. 3.] ist 6.0
Mit NumPy-Arrays lassen sich außerdem elementare Rechenoperationen direkt durchführen:
a+barray([7., 7., 7.])b-aarray([5., 3., 1.])a*barray([ 6., 10., 12.])a/barray([0.16666667, 0.4 , 0.75 ])Wir beobachten, dass diese Operationen elementweise ausgeführt werden.
Bei Addition und Subtraktion entspricht dies genau der Definition der Vektoraddition aus der linearen Algebra. Bei Multiplikation und Division ist dies jedoch weniger offensichtlich – hier hätte man möglicherweise das Skalarprodukt oder das Kreuzprodukt erwartet. Diese Operationen werden wir später kennenlernen.
Die Rechenoperationen +, -, * und / funktionieren nur dann, wenn die beteiligten Arrays die gleiche Form (shape) besitzen. Andernfalls tritt ein Fehler auf:
c = np.array([8,7,6,5])
a+c---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[13], line 2
1 c = np.array([8,7,6,5])
----> 2 a+c
ValueError: operands could not be broadcast together with shapes (3,) (4,) Untersuchen wir nun, welche Methoden die Klasse ndarray bereitstellt. Dazu geben wir a.<TAB> in einem Notebook ein oder rufen die Funktion dir(a) auf. In beiden Fällen erhalten wir eine lange Liste verfügbarer Methoden.
Testen wir einige dieser Methoden:
print("Der kleinste Eintrag von a ist", a.min(), "bei Index", a.argmin())
print("Das Skalarprodukt <a,b> ist", a.dot(b))
print("Die Summe aller Einträge von a ist", a.sum())Der kleinste Eintrag von a ist 1.0 bei Index 0
Das Skalarprodukt <a,b> ist 28.0
Die Summe aller Einträge von a ist 6.0
Weitere Rechenoperationen¶
Neben den elementaren Methoden, welche die Klasse ndarray direkt bereitstellt, gibt es zahlreiche weitere Rechenoperationen, die als freie Funktionen in der Bibliothek numpy implementiert sind.
Geben wir np.<TAB> ein, erhalten wir eine Liste dieser Funktionen. Dabei fällt auf, dass hier ebenfalls Funktionen wie sqrt, exp, sin oder cos definiert sind. Diese existieren zwar auch in der Bibliothek math, lassen sich dort jedoch nicht auf NumPy-Arrays anwenden:
import math
math.exp(a)---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[15], line 2
1 import math
----> 2 math.exp(a)
TypeError: only 0-dimensional arrays can be converted to Python scalarsDies führt zu einem Fehler, da math.exp nur für skalare Werte definiert ist.
Die entsprechende Funktion aus der numpy-Bibliothek hingegen wird elementweise auf das Array angewendet:
np.exp(a)array([ 2.71828183, 7.3890561 , 20.08553692])Dabei wird die Exponentialfunktion auf jede Komponente des Vektors angewendet.
Auch andere Funktionen wie log, sin, cos, tan, abs usw. werden elementweise auf NumPy-Arrays angewendet.
Ebenso funktionieren Vergleichsoperationen elementweise:
x = np.array([0,1,2,3,4])
y = (x <=2)
yarray([ True, True, True, False, False])Das Ergebnis ist ein Array von boolschen Werten, welches für jedes Element von x angibt, ob die Bedingung erfüllt ist.
Neben diesen elementweisen Rechenoperationen finden wir im Modul numpy auch vektorspezifische Operationen, wie beispielsweise das Skalarprodukt oder das Kreuzprodukt.
np.dot(a,b)np.float64(28.0)np.cross(a,b)array([-7., 14., -7.])Bei einer genaueren Betrachtung der Funktionen im Modul numpy stellt man fest, dass viele Operationen aus der linearen Algebra im Submodul numpy.linalg organisiert sind. Dazu gehören beispielsweise Funktionen zur Berechnung von Normen, Determinanten oder Matrixinversen.
Geben wir
np.linalg.<TAB>ein, erhalten wir eine Liste der verfügbaren Funktionen.
Mit der Funktion np.linalg.norm lassen sich verschiedene Vektornormen berechnen:
print("Euklidische Norm :", np.linalg.norm(a))
print("Maximumnorm :", np.linalg.norm(a, np.inf))
print("1-Norm :", np.linalg.norm(a, 1))Euklidische Norm : 3.7416573867739413
Maximumnorm : 3.0
1-Norm : 6.0
Zugriff auf Vektoreinträge¶
Schauen wir uns zuletzt noch an, wie auf einzelne oder mehrere Elemente eines NumPy-Arrays zugegriffen werden kann. Dies geschieht – wie auch bei Listen – mit dem []-Operator:
a = np.linspace(0,1,11)
aarray([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])print("Vierter Eintrag:") # Die Zählung des Index' beginnt bei 0
a[3]Vierter Eintrag:
np.float64(0.30000000000000004)Wir können auch einen Teil des Arrays extrahieren, indem wir einen Indexbereich i:j angeben:
a[3:6] # Indizes zwischen 3 und 5array([0.3, 0.4, 0.5])Dabei gilt allgemein:
iist der Startindex (inklusive)jist der Endindex (exklusive)
Weitere Beispiele:
a[:6] # Indizes bis 5array([0. , 0.1, 0.2, 0.3, 0.4, 0.5])a[6:] # Indizes ab 6array([0.6, 0.7, 0.8, 0.9, 1. ])Wir können auch Teilvektoren überschreiben, natürlich unter Beachtung der Dimension:
a[1:4] = np.ones((3,)) # Schreibe Einsen in die Einträge 1 bis 3
a[6:8] = np.zeros((2,)) # Schreibe Nullen in die Einträge 6 und 7
aarray([0. , 1. , 1. , 1. , 0.4, 0.5, 0. , 0. , 0.8, 0.9, 1. ])Dabei haben wir nebenbei zwei weitere Funktionen kennengelernt, mit denen sich Arrays mit konstanten Einträgen erzeugen lassen:
np.ones(shape)erzeugt ein Array der angegebenen Form, dessen Einträge alle 1 sind.np.zeros(shape)erzeugt ein Array der angegebenen Form, dessen Einträge alle 0 sind.
Arbeiten mit Matrizen¶
Erzeugen von Matrizen¶
Auch für Matrizen verwenden wir den Datentyp numpy.ndarray. Zur Initialisierung übergeben wir der Funktion np.array eine Liste von Listen, wobei jede innere Liste einer Zeile der Matrix entspricht. NumPy erkennt automatisch, dass ein zweidimensionales Array, also eine Matrix, erzeugt werden soll:
A = np.array([[1.,4.,2.],[2.,0.,3.],[1.,1.,4.]])
print("A ist vom Typ:", type(A))
print("A hat die Form:", A.shape)
print(A)A ist vom Typ: <class 'numpy.ndarray'>
A hat die Form: (3, 3)
[[1. 4. 2.]
[2. 0. 3.]
[1. 1. 4.]]
Die Form der Matrix (
A.shape) gibt die Anzahl der Zeilen und Spalten an, also(3,3)für dieses Beispiel.Intern ist
Aweiterhin einndarray, nur mit zwei Dimensionen (A.ndim == 2).Genau wie bei Vektoren können wir auf einzelne Elemente oder Teilbereiche zugreifen, z.B.
A[0,1]für Zeile 0, Spalte 1.
Es gibt mehrere Möglichkeiten, Matrizen in NumPy zu erzeugen:
Einheitsmatrix
Eine quadratische Einheitsmatrix kann direkt übernp.eye(n)erzeugt werden:
B = np.eye(3)
Barray([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])Leere Matrix und manuelles Befüllen
Mitnp.zeros((m,n))kann eine Matrix der Größem×nerzeugt werden, deren Einträge zunächst 0 sind. Anschließend können einzelne Einträge direkt zugewiesen werden:
C = np.zeros((3,3)) # Das Argument ist ein Tupel und gibt die Größer der Matrix an
C[0,0] = 1
C[1,1] = 2
C[1,2] = 5
C[2,1] = 4
C[2,2] = 1
Carray([[1., 0., 0.],
[0., 2., 5.],
[0., 4., 1.]])Diagonalmatrix aus einem Vektor
Aus einem gegebenen Vektorvlässt sich eine Diagonalmatrix erzeugen, bei der die Elemente vonvauf der Hauptdiagonalen stehen:
D = np.diag(np.array([1.,2.,3.]))
Darray([[1., 0., 0.],
[0., 2., 0.],
[0., 0., 3.]])Rechnen mit Matrizen¶
Bei Matrizen funktionieren die elementaren Rechenoperationen ähnlich wie bei Vektoren: Addition, Subtraktion, Multiplikation und Division sowie viele Funktionen aus dem Paket numpy (exp, sin, cos, log etc.) werden elementweise auf die Matrizen angewendet.
C = A+B
Carray([[2., 4., 2.],
[2., 1., 3.],
[1., 1., 5.]])D = A*B
Darray([[1., 0., 0.],
[0., 0., 0.],
[0., 0., 4.]])E = np.exp(A)
Earray([[ 2.71828183, 54.59815003, 7.3890561 ],
[ 7.3890561 , 1. , 20.08553692],
[ 2.71828183, 2.71828183, 54.59815003]])Weitere Rechenoperationen werden von der Klasse numpy.ndarray bereitgestellt, wie das Transponieren einer Matrix:
C.transpose()array([[2., 2., 1.],
[4., 1., 1.],
[2., 3., 5.]])Für lineare Algebra-Operationen nutzt man die Funktionen aus numpy oder numpy.linalg:
Matrixmultiplikation:
np.matmul(A,B) # Freie Funktion
A@B # Alternativ mit dem @-Operatorarray([[1., 4., 2.],
[2., 0., 3.],
[1., 1., 4.]])Determinante:
np.linalg.det(C)np.float64(-22.000000000000004)Inversen:
np.linalg.inv(C)array([[-0.09090909, 0.81818182, -0.45454545],
[ 0.31818182, -0.36363636, 0.09090909],
[-0.04545455, -0.09090909, 0.27272727]])Eigenwerte- und Vektoren:
E,V = np.linalg.eig(C)
print("Eigenwerte :\n", E)
print("Eigenvektoren :\n", V)Eigenwerte :
[ 6.97413644 -1.33574651 2.36161007]
Eigenvektoren :
[[ 0.63932126 0.77319109 -0.8514755 ]
[ 0.50515119 -0.63379132 -0.29406667]
[ 0.57973321 -0.02200211 0.43418229]]
Hinweis: Die Eigenvektoren stehen spaltenweise in
V. Testen wir die Rechnung indem wir überprüfen:
for i in range(3):
res = np.matmul(C, V[:,i]) - E[i]*V[:,i] # C*v-lambda*v
print("Fehler: ", np.linalg.norm(res))Fehler: 3.66205343881779e-15
Fehler: 1.5852031831917945e-15
Fehler: 2.434909185329438e-15
Lösen linearer Gleichungssysteme:
b = np.array([24.,23.,30.])
x = np.linalg.solve(C, b)
print("x =", x)
# Probe:
np.matmul(C,x)-bx = [3. 2. 5.]
array([ 0.00000000e+00, 0.00000000e+00, -3.55271368e-15])Zugriff auf Matrixeinträge
Der Zugriff auf einzelne Einträge oder Teilmatrizen funktioniert analog zu eindimensionalen ndarray-Arrays:
print("Eintrag C_11 :", C[0,0])
print("Zweite Spalte :", C[:,1])
print("Dritte Zeile :", C[2,:])
print("Letzte (=dritte) Zeile :", C[-1,:])Eintrag C_11 : 2.0
Zweite Spalte : [4. 1. 1.]
Dritte Zeile : [1. 1. 5.]
Letzte (=dritte) Zeile : [1. 1. 5.]
Matrizen stapeln
Mit den Funktionen numpy.vstack und numpy.hstack lassen sich Matrizen vertikal bzw. horizontal zusammenfügen:
np.vstack([A,B,C]) # Stapelt A, B, C vertikalarray([[1., 4., 2.],
[2., 0., 3.],
[1., 1., 4.],
[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.],
[2., 4., 2.],
[2., 1., 3.],
[1., 1., 5.]])np.hstack([A,B,C]) # Stapelt A, B, C horizontalarray([[1., 4., 2., 1., 0., 0., 2., 4., 2.],
[2., 0., 3., 0., 1., 0., 2., 1., 3.],
[1., 1., 4., 0., 0., 1., 1., 1., 5.]])Möchte man einen Vektor an eine Matrix “kleben”, muss der Vektor ggf. zunächst in eine -Matrix umgeformt werden:
np.vstack([A,b])array([[ 1., 4., 2.],
[ 2., 0., 3.],
[ 1., 1., 4.],
[24., 23., 30.]])np.hstack([A,b.reshape((3,1))])array([[ 1., 4., 2., 24.],
[ 2., 0., 3., 23.],
[ 1., 1., 4., 30.]])Hinweis:
reshape((n,1))formt den Vektor in eine Spaltenmatrix um, damithstackkorrekt funktioniert.
Mutable oder Immutable?
NumPy-Arrays verhalten sich als mutable Objekte. Das bedeutet, dass Änderungen innerhalb von Funktionen oder über neue Variablen das ursprüngliche Array verändern können:
def modify_matrix(A):
A[1,1] = 1.
A = np.zeros((3,3))
modify_matrix(A)
print(A)[[0. 0. 0.]
[0. 1. 0.]
[0. 0. 0.]]
Das Array
Awurde durch die Funktion verändert, dandarraymutable ist.
Achten wir auf eine einfache Zuweisung:
B = A
B[1,1] = 2.
Aarray([[0., 0., 0.],
[0., 2., 0.],
[0., 0., 0.]])Hier zeigt der Name B auf dasselbe Objekt wie A. Änderungen über B wirken sich also direkt auf A aus.
Möchte man eine echte Kopie erstellen, die unabhängig vom Original ist, verwendet man:
B = np.copy(A)
B[1,1] = 3.
print(A)[[0. 0. 0.]
[0. 2. 0.]
[0. 0. 0.]]
Änderung an
BbeeinflusstAnicht, danp.copyein neues Array erzeugt.
NumPy-Glossar – Vektoren & Matrizen¶
Erzeugung von Arrays¶
| Funktion | Beschreibung | Beispiel |
|---|---|---|
np.array(seq) | Wandelt iterierbares Objekt in Array um | np.array([1,2,3]) |
np.zeros(shape) | Array gefüllt mit Nullen | np.zeros((3,3)) |
np.ones(shape) | Array gefüllt mit Einsen | np.ones(5) |
np.eye(n) | Einheitsmatrix (n×n) | np.eye(3) |
np.diag(v) | Diagonalmatrix aus Vektor v | np.diag([1,2,3]) |
np.linspace(start, stop, num) | Gleichmäßig verteilte Werte | np.linspace(0,1,5) |
np.arange(start, stop, step) | Werte in festem Schritt | np.arange(0,3,0.5) |
Basis-Attribute von Arrays (ndarray)¶
| Attribut | Bedeutung |
|---|---|
.ndim | Anzahl Dimensionen |
.shape | Form / Größe des Arrays |
.size | Anzahl Elemente |
.dtype | Datentyp der Elemente |
Elementweise Operationen¶
| Operator / Funktion | Beschreibung |
|---|---|
+,-,*,/ | Elementweise Addition, Subtraktion, Multiplikation, Division |
np.exp(a) | Elementweise Exponentialfunktion |
np.log(a) | Elementweise natürliche Logarithmus |
np.sin(a), np.cos(a), np.tan(a) | Elementweise trigonometrische Funktionen |
Vergleichsoperatoren (<, <=, >, >=, ==, !=) | Elementweise |
Methoden von ndarray¶
| Methode | Beschreibung |
|---|---|
.sum() | Summe aller Elemente |
.min() | Kleinster Wert |
.max() | Größter Wert |
.argmin() | Index des kleinsten Werts |
.argmax() | Index des größten Werts |
.dot(b) | Skalarprodukt (nur Vektoren) |
.transpose() | Transponierte Matrix |
Freie Funktionen in numpy¶
| Funktion | Beschreibung |
|---|---|
np.dot(a,b) | Skalarprodukt / Matrix-Vektor-Produkt |
np.cross(a,b) | Kreuzprodukt (3D-Vektoren) |
np.matmul(a,b) | Matrixmultiplikation |
np.vstack([A,B]) | Vertikales Stapeln von Matrizen |
np.hstack([A,B]) | Horizontales Stapeln von Matrizen |
Funktionen in numpy.linalg (lineare Algebra)¶
| Funktion | Beschreibung |
|---|---|
np.linalg.norm(a, ord=2) | Vektornorm (Standard: 2-Norm) |
np.linalg.inv(A) | Inverse einer Matrix |
np.linalg.det(A) | Determinante |
np.linalg.eig(A) | Eigenwerte und Eigenvektoren |
np.linalg.solve(A,b) | Löst lineares Gleichungssystem Ax=b |
Zugriff auf Elemente¶
| Syntax | Bedeutung |
|---|---|
a[i] | i-ter Eintrag eines Vektors |
a[i:j] | Einträge i bis j-1 |
a[:j] | Einträge 0 bis j-1 |
a[i:] | Einträge i bis Ende |
A[i,j] | Eintrag (i,j) einer Matrix |
A[i,:] | Zeile i |
A[:,j] | Spalte j |
A[i:j, k:l] | Teilmatrix |
Mutable Verhalten¶
NumPy-Arrays sind mutable. Änderungen innerhalb von Funktionen oder über Zuweisungen wirken sich auf das Original-Array aus.
Für echte Kopien:
B = np.copy(A)