numpy

Lernziele

  • Verstehen, warum Arrays verwendet werden sollten
  • Allgemeine numerische Operationen für Arrays
    • Broadcasting-Regeln
    • Slicing
    • Lineare Algebra

Numerische Berechnung und Python

Die große Flexibilität und Benutzerfreundlichkeit von Python machen es zu einer großartigen Allzwecksprache. Da es sich um eine interpretierte Sprache (keine kompilierte Sprache wie beispielsweise C++ oder Fortran) mit einem flexiblen Typsystem handelt, ist es sehr einfach Code zu schreiben.

Motivation zur Nutzung von NumPy

Das Python-Modul NumPy definiert einen neuen Objekttyp – das sogenannte Array – das es uns ermöglicht, die Flexibilität von Python mit der Effizienz kompilierter, näher an der Hardware orientierter Sprachen, zu kombinieren.

Das folgende einfache Beispiel veranschaulicht den Geschwindigkeitsgewinn, der normalerweise bei der Verwendung von NumPy zu erwarten ist. Gegeben eine Reihe von Daten \[ t = (0, 0,001, 0,002, \cdots, 0,999),\] berechnen wir \(y = \sin(\omega\,t + \varphi)\), zuerst mit den Standard-Python-Tools – Comprehensions und dem Modul math – und dann mit dem NumPy-Modul.

import math
import numpy as np
omega = 24.5
phi = np.pi/6
nb_pts = 1000
%%timeit
# Nur Verwendung von Standard-Python-Bibliotheken
t = [i/nb_pts for i in range(nb_pts)]
y = [math.sin(t_val*omega + phi) for t_val in t]
82.6 μs ± 1.5 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
%%timeit
# Numpy-Arrays verwenden
t = np.arange(nb_pts)/nb_pts
y = np.sin(t*omega + phi)
6.58 μs ± 95.8 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Die Anweisung %%timeit ist eine Notebook-Funktion, die die Zeit auswertet, die zum Auswerten der Zelle benötigt wird. Wie Sie sehen können, ist die entsprechende Berechnung bei Verwendung von Numpy-Arrays etwa eine Größenordnung schneller. Am Ende der heutigen Sitzung sollten Sie wissen, wann und wie Sie diese zur Beschleunigung Ihrer numerischen Berechnungen verwenden können.

Das grundlegende Konstrukt, das NumPy eine so gute Leistung ermöglicht, ist numpy.array, ein homogenes Array von Daten eines einzelnen Typs. Da alle Datenpunkte in einem solchen Array vom gleichen Typ sind und ohne Sprünge oder Unterbrechungen nacheinander im Speicher liegen (beides ist für Python-Listen nicht garantiert), kann NumPy viele Optimierungen vornehmen, wenn es um Vorgänge geht, die das Durchlaufen dieser Mengen beinhalten.

NumPy ist ein riesiges Modul, und wir können nur die Oberfläche berühren. Von Ihnen wird erwartet, dass Sie mithilfe von NumPy documentation und der Notebook-Hilfe selbst etwas lernen, wenn Sie nicht weiterkommen. Bitte schauen Sie sich auch das folgende Video an, das eine kurze Einführung in Numpy gibt.

%%HTML
<center>
<iframe src="https://uni-freiburg.cloud.panopto.eu/Panopto/Pages/Embed.aspx?id=0fe8b95c-0783-4eb9-9a1f-ab9a00eb6203&autoplay=false&offerviewer=false&showtitle=true&showbrand=false&start=0&interactivity=all" width=720 height=405 style="border: 1px solid # 464646;"allowfullscreenallow="autoplay"></iframe>
</center>

Array-Erstellung

Es gibt einige Funktionen, mit denen Sie Arrays erstellen können. Die wichtigsten sind np.array(), np.linspace(), np.arange(), np.zeros() und np.ones().

import numpy as np
# Verwenden Sie `array`, wenn Sie die Daten bereits in irgendeiner Form haben
a = np.array([12, 24.5, 15.3])
a
array([12. , 24.5, 15.3])
# Arrays können mehr als eine Dimension haben
b = np.array([[1, 2, 7], [8, 24, -6]])
b
array([[ 1,  2,  7],
       [ 8, 24, -6]])
# Verwenden Sie `linspace` und `arange`, um beabstandete Gitterpunkte zu generieren
print(np.linspace(5, 7.5, 6))
print(np.arange(0, 1., .1))
[5.  5.5 6.  6.5 7.  7.5]
[0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]
# Verwenden Sie `zeros` und `ones`, um ein Array mit einheitlichen Werten zu initiieren
print(np.zeros(4))
print(np.ones((3, 2)))
[0. 0. 0. 0.]
[[1. 1.]
 [1. 1.]
 [1. 1.]]

NumPy-Arrays verfügen über einige wichtige Attribute:

Attribut Beschreibung
dtype Geben Sie Informationen zu den Daten ein
shape Tupel von Ganzzahlen, die die Anzahl der Einträge entlang einer beliebigen Achse darstellen

Wenn Sie aufgepasst haben, werden Sie bemerkt haben, dass das Array a ​​Gleitkommawerte enthält, das Array b jedoch nur Ganzzahlen. Dieser Datentyp (dtype) ist das wichtigste Attribut. Es bestimmt die Größe des Arrays und die Operationen, die auf die Datenpunkte angewendet werden können. Bei numerischen Berechnungen werden Sie meist Arrays mit Gleitkommawerten verwenden.

print("dtype von a ist", a.dtype)
print("dtype von b ist", b.dtype)
dtype von a ist float64
dtype von b ist int64

Achten Sie auf den Datentyp; Das Erstellen von Arrays mit dem falschen dtype kann zu überraschenden und schwer zu findenden Fehlern führen

int_arr = np.array([1, 1, 2])
print(int_arr)
[1 1 2]
int_arr[2] = 5.7
print(int_arr)
[1 1 5]

Wenn Sie versuchen, einem Ganzzahl-Array einen Gleitkommawert zuzuweisen, wird dieser abgeschnitten. Im Zweifelsfall ist es ratsam, den Datentyp eines Arrays explizit auszuwählen:

float_arr = np.array([1, 1, 2], dtype=float)
float_arr[2] = 5.7
print(float_arr)
[1.  1.  5.7]

Das Attribut shape gibt Ihnen die Größe des Arrays in jeder seiner Dimensionen an

a.shape # eindimensionales Array: hat nur eine Länge
(3,)
b.shape # zweidimensionales Array, hat 3 Spalten und 2 Zeilen
(2, 3)

Aufgaben

  • Verwenden Sie die Notebook-Hilfe, um zu verstehen, was die Funktionen np.array(), np.linspace(), np.arange(), np.zeros() und np.ones() bewirken. – Die Hilfe zu np.linspace() erwähnt eine effiziente Möglichkeit, Daten zu generieren, die gleichmäßig im Protokollraum verteilt sind. Verwenden Sie es, um ein Array zu generieren, das 12 logarithmisch verteilte Datenpunkte zwischen 1 und 500 enthält

Indizierung

NumPy-Arrays können ähnlich wie Listen und Strings indiziert werden:

a = np.arange(5)
a
array([0, 1, 2, 3, 4])
a[::-1]
array([4, 3, 2, 1, 0])
a[1:4]
array([1, 2, 3])
a[::2]
array([0, 2, 4])

Allerdings können NumPy-Arrays mehrere Dimensionen haben, ebenso wie ihre Indizes:

a = np.arange(9)
a.shape = (3, 3)
a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
a[1:, :]
array([[3, 4, 5],
       [6, 7, 8]])
a[:, ::-1]
array([[2, 1, 0],
       [5, 4, 3],
       [8, 7, 6]])

Eine wichtige und äußerst nützliche Eigenschaft indizierter Arrays (sogenannte Slices von Arrays) besteht darin, dass sie keine Kopie der Originaldaten sind, sondern lediglich eine Ansicht darauf. Das folgende Beispiel verdeutlicht dies:

# Erstellen Sie ein Array
a = np.ones((4, 4))
print(a)
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]
# Schneiden Sie es in Scheiben, um eine Ansicht einer Unterregion zu erhalten
b = a[:2, 1:3]
print(b)
[[1. 1.]
 [1. 1.]]
# Ändern Sie den Inhalt des Slice
b += 12
print(b)
[[13. 13.]
 [13. 13.]]
# denn das Slice ist keine Kopie, sondern eine Ansicht darauf
# Originaldaten, das Array a wurde ebenfalls geändert
print(a)
[[ 1. 13. 13.  1.]
 [ 1. 13. 13.  1.]
 [ 1.  1.  1.  1.]
 [ 1.  1.  1.  1.]]

Arithmetik und Broadcasting

Operationen an Arrays werden elementweise ausgeführt. Betrachten Sie die folgenden Beispiele:

a = np.arange(4, dtype=float)
a
array([0., 1., 2., 3.])
a+2
array([2., 3., 4., 5.])
a*5
array([ 0.,  5., 10., 15.])
a + np.array([12, 5, .3, 5])
array([12. ,  6. ,  2.3,  8. ])
a ** 2 + 2*a
array([ 0.,  3.,  8., 15.])

Das Konzept ist trivial für den Fall, dass wir Skalare und Arrays oder Arrays derselben Form mischen. Elementweise Operationen sind jedoch nicht auf diesen Fall beschränkt: NumPy kann Arrays zusammen broadcasten, wenn sie kompatible Formen haben. Die Regeln sind (aus dem NumPy doc)

Bei der Arbeit mit zwei Arrays vergleicht NumPy deren Formen (Shapes) elementweise. Es beginnt mit den abschließenden Dimensionen und arbeitet sich nach vorne vor. Zwei Dimensionen sind kompatibel, wenn

  1. sie sind gleich, oder
  2. eine davon ist 1

Außerdem,

Arrays müssen nicht die gleiche Anzahl an Dimensionen haben. Wenn Sie beispielsweise über ein 256 x 256 x 3 großes Array mit RGB-Werten verfügen und jede Farbe im Bild um einen anderen Wert skalieren möchten, können Sie das Bild mit einem eindimensionalen Array mit drei Werten multiplizieren. Wenn man die Größen der nachgestellten Achsen dieser Arrays gemäß den Broadcast-Regeln anordnet, zeigt sich, dass sie kompatibel sind:

Bild (3D-Array): 256 x 256 x 3
Maßstab (1D-Array): 3
Ergebnis (3D-Array): 256 x 256 x 3
a = np.arange(4)
a.shape = (2, 2)
a
array([[0, 1],
       [2, 3]])
b = np.array([[8], [9]])
a + b
array([[ 8,  9],
       [11, 12]])

Aufgaben

  • Erklären Sie, was hier passiert ist. Fragen Sie uns, wenn Sie Fragen haben.
  • Erstellen Sie ein Array mit der Form und dem Muster eines Schachbretts mit Nullen für schwarze und Einsen für weiße Felder (das A-1-Feld eines Schachbretts ist schwarz).
  • Vervollständigen Sie das Code-Schnipsel anhand Ihrer Erkenntnisse über Übertragungsregeln > python > a = np.arange(4) > b = 10*a > b.shape = ????? > print(a+b) > damit das Ergebnis ist > > [[ 0 1 2 3] > [10 11 12 13] > [20 21 22 23] > [30 31 32 33]] >
%%HTML
<center>
<iframe src="https://uni-freiburg.cloud.panopto.eu/Panopto/Pages/Embed.aspx?id=0fd150f2-1321-4f48-bd1c-aba100da23c8&autoplay=false&offerviewer=false&showtitle=true&showbrand=false&start=0&interactivity=all" width=720 height=405 style="border: 1px solid # 464646;"allowfullscreenallow="autoplay"></iframe>
</center>

Grundlegende lineare Algebra

Arrays können zur Darstellung von Vektoren und Matrizen verwendet werden und NumPy bietet Funktionen, um grundlegende lineare Algebra-Operationen auf ihnen durchzuführen (siehe Numpy Dokumentation). Beispielsweise verwenden Matrixmultiplikationen und Matrix-Vektor-Multiplikationen die Funktion np.dot():

a = np.array([[1, 0], [0, 1]])
b = np.array([[4, 1], [2, 2]])
np.dot(a, b)
array([[4, 1],
       [2, 2]])
c = np.array([1, 8])
np.dot(b, c)
array([12, 18])

Die Inverse der Matrix kann mit np.linalg.inv() berechnet werden

np.linalg.inv(b)
array([[ 0.33333333, -0.16666667],
       [-0.33333333,  0.66666667]])

Zweidimensionale Matrizen können auch bequem transponiert werden

b
array([[4, 1],
       [2, 2]])
b.T
array([[4, 2],
       [1, 2]])

Oder Eigenwerte und -vektoren werden konventionell mit np.linalg.eig berechnet

values, vectors = np.linalg.eig(b)
values
array([4.73205081, 1.26794919])
vectors
array([[ 0.80689822, -0.34372377],
       [ 0.59069049,  0.9390708 ]])

Aufgaben

  • Lösen Sie das folgende Gleichungssystem mit der linearen Algebra von NumPy

\[ \begin{align} 12\,x + 16\,y &= 4\\ 8\, x + 12\, y &=0 \end{align} \]

  • Lesen Sie mehr über np.linalg.solve und nutzen Sie es, um das System effizienter und kompakter zu lösen.
  • Verwenden Sie dazu das Modul linalg von NumPy
    1. Finden Sie die Hauptspannungen,
    2. Finden Sie die Richtung der kleinsten Hauptspannung.
    Gegeben sei ein Spannungstensor

\[ \boldsymbol\sigma = \left[ \begin{array}{cc} 15 & -35 \\ -35 & 15 \end{array} \right]\ \mathrm{MPa} \]