JGU Logo JGU Logo JGU Logo JGU Logo

Institut für Informatik

Modellierung 2

Michael Wand
Ann-Christin Wörl & David Hartmann
Sommersemester 2023

Lehreinheit 1

PyTorch & etwas Zufall
Letzte Änderung: 17. April 2023, 13:41 Uhr
Abgabe: Montag, der keinen Abgabe

  /  



PyTorch: An Imperative Style, High-PerformanceDeep Learning Library

In diesem Paper geht es um Pytorch! Wenn Sie mich fragen, hat das Programmieren von tiefen Netzen in Python (und vorher in Lua mit Torch) massiv zum rasanten Anstieg an forschungen in Deep Learning beigetragen.

3 Dec 2019

I've been using PyTorch a few months now and I've never felt better. I have more energy. My skin is clearer. My eye sight has improved.

Andrej Karpathy

Director of AI at Tesla, previously a research scientist at OpenAI.


Meta-Talk

Willkommen auch meinerseits zur Veranstaltung „Modellierung 2“!
Bevor wir direkt mit dem Übungsblatt starten hier die wichtigsten Randpunkte zum weiteren Ablauf.


Über diese Webseite: Die Übungsaufgaben dieser Veranstaltungen sind etwas freier gestaltet;
Konzept der Übung Die Übungsaufgaben in diesem Semester zu „Modellierung II“ sind geziehlt forschungsnäher konzipiert;


Aufgabe 0: PyTorch Tutorial


  1. Installieren Sie Pytorch.
    Die nötigen Schritte für Ihr System sind hier zu finden: pytorch.org/get-started/locally
  2. Testen Sie auch, ob Pytorch korrekt installiert ist.
    (Siehe Unterpunkt „Verification“ im „Get-Started“ Guide)
  3. Arbeiten Sie die angegebenen offiziellen Tutorials / Dokuseiten von Pytorch durch.
    Keine Angst, hier wurde bereits nur das Nötigste vorausgewählt und ganz im Ernst; jeder Versuch die Dokumentation oder die Tutorials neu aufzuarbeiten oder zu kopieren würde Ihnen wichtige Informationen vorenthalten. Aus dem Grund werden Sie hier direkt auf die Dokumentation verwiesen, da ohnehin dort gesucht werden muss, um das Toolset in seiner Gänze verwenden zu können.
    Wir benötigen im Moment lediglich die folgenden Themen:
    1. Learn the Basics/Tensors.
      (Quickstart kann hier übersprungen werden).
    2. Broadcasting
    3. Indexing
    4. Reshaping
    5. Optional: Hier gibt es einige Puzzles (wie unten in Aufgabe 1), zusätzilch kann mein seine eigene Lösung auch visuell darstellen lassen: https://github.com/srush/Tensor-Puzzles

Aufgabe 1: PyTorch Basics


Das Ziel ist es sich hier mit vektorisierter Rechnung auseinanderzusetzen, also in keiner der Aufgaben Python-for Schleifen zu benutzen!


  1. Erstellen von Tensoren & einfache Rechnungen
    1. Erstellen Sie einen Tensor der ersten 1'000'000 Quadratzahlen.
    2. Erstellen Sie einen zufälligen Tensor der gleichen Größe mit zufälligen Zahlen zwischen 0 und 1 und sortieren Sie diesen.
  2. Broadcasting
    1. Erstellen Sie eine Multiplikations/„Ein-Mal-Eins“ Tabelle mithilfe von Broadcasting, also:
      tensor([[   1,   2,   3,   4,   5,   6,   7,   8,   9,  10],
       [   2,   4,   6,   8,  10,  12,  14,  16,  18,  20],
       [   3,   6,   9,  12,  15,  18,  21,  24,  27,  30],
       [   4,   8,  12,  16,  20,  24,  28,  32,  36,  40],
       [   5,  10,  15,  20,  25,  30,  35,  40,  45,  50],
       [   6,  12,  18,  24,  30,  36,  42,  48,  54,  60],
       [   7,  14,  21,  28,  35,  42,  49,  56,  63,  70],
       [   8,  16,  24,  32,  40,  48,  56,  64,  72,  80],
       [   9,  18,  27,  36,  45,  54,  63,  72,  81,  90],
       [  10,  20,  30,  40,  50,  60,  70,  80,  90, 100]])
      
    2. Erstellen Sie einen zufälligen 2d-Tensor aus \(\{0,1\}^{1000\times 1000}\) und teilen Sie alle Zahlen durch die jeweilige Spaltensumme.
      Verifizieren Sie, dass die Spaltensumme des resultierenden Tensors stets 1 ist.
  3. Reshaping & Indizierung
    1. Erstellen Sie eine 2d-Tensor (beispelsweise der Größe \(10 \times 10\)), der mit zufälligen ganzen Zahlen zwischen 1 und 10 gefüllt ist.
      Geben Sie jeweils einen Ausdruck an, der
    2. alle geraden Zahlen auf 0 setzt
    3. den Anteil gerader Zahlen in einer beliebigen Matrix zählt
    4. alle Spalten quadriert, die mindestens eine 5 enthalten
    5. alle Diagonaleinträge auf 0 setzt.
    6. die erste Spalte und die erste Zeile vertauscht
    7. Erstellen Sie eine Funktion, die einen 2d-Tensor aus \(\{0,1\}^{n\times n}\) zurückgibt, der wie ein Schachbrett gefüllt ist.

      Hinweis: torch.stack oder reshaping und modulo-Operator. Beispiel:
      > chess(9)
        tensor([[1, 0, 1, 0, 1, 0, 1, 0, 1],
                [0, 1, 0, 1, 0, 1, 0, 1, 0],
                [1, 0, 1, 0, 1, 0, 1, 0, 1],
                [0, 1, 0, 1, 0, 1, 0, 1, 0],
                [1, 0, 1, 0, 1, 0, 1, 0, 1],
                [0, 1, 0, 1, 0, 1, 0, 1, 0],
                [1, 0, 1, 0, 1, 0, 1, 0, 1],
                [0, 1, 0, 1, 0, 1, 0, 1, 0],
                [1, 0, 1, 0, 1, 0, 1, 0, 1]], dtype=torch.int32)
      


    1. Erstellen Sie eine Funktion, die einen „Hypercube“ der Kantenlänge 3 und Dimension n erstellt, bei dem jeweils überall eine 1 steht, außer im Zentrum, dort soll eine 0 stehen.
    2. Erstellen Sie eine Funktion, die einen „Hypercube“ der Kantenlänge 3 und Dimension n erstellt, bei dem jeweils eine 1 auf jeder Ecke steht, ansonsten aber nur mit 0en gefüllt ist.
      Hinweis: Das Schwierige ist hier insbesondere keine Schleife zu verwenden. Konstruieren Sie zum Beispiel den Hypercube also für kleine Dimensionen per Hand, flatten (oder .reshape(-1)en) Sie den Würfel und prüfen Sie in welcher Frequenz sich die 1en befinden.
  4. Zusammengenommen: Implementieren Sie die Mandelbrotmenge mithilfe von Pytorch.
    Visualisieren Sie sie beispelsweise mit Matplotlib. Der Sinn hierbei ist Jupyter/Colab auszutesten, denn in diesen Tools können Sie ihre Ergebnisse stehts griffbereit präsentieren.

    Ein paar Hinweise:
    Erstellen Sie dazu ein Gitter auf der komplexen Ebene. Starten Sie beispielsweise mit dem komplexen Interval \([-2,1]\times [-1,1]\).
    (Pytorch Implementiert auch komplexe Zahlen)

    Das Gitter stellt dabei unseren Computerbildschirm mit (\(w\times h\) Pixeln) dar. Führen Sie die folgende Rechnung \(n\)-fach (\(n=250\) liefert gute Ergebnisse) auf dem Gitter \(c\) aus:

    \[ z_{n+1} = z_n^2 + c \]

    (Sie können diese Zeile fast direkt so abtippen. Hier ist es natürlich erlaubt eine Schleife zu benutzen).

    Nun müssen wir lediglich testen, ob die Folge divergiert. Es reicht hierbei aus zu prüfen, ob \(|z_n| > 2\) ist.

    Plotten Sie als nächstes das Gitter mit Matplotlib:


    import matplotlib.pyplot as plt
    import torch
    
    # diese Zeile müssen Sie natürlich gegen das Ergebnis der oberen Formel ersetzen.
    data = torch.randn((100,100))
    
    plt.pcolormesh(data, cmap="viridis", linewidth=0, shading='flat')
    plt.plot()
    plt.show()
    

Aufgabe 2: Generierung von „Zufall“


Aufgaben:


  1. Schreiben Sie jeweils eine Funktion, die eine Zufallsvariable mit folgender Verteilung ausgibt.
    Achten Sie dabei darauf, dass Sie direkt \(n\) viele dieser Experimente "auf einmal" durchführen, also vektorisiert rechnen.
    Die einfachste Möglichkeit reicht hier aus. Es ist hier natürlich nicht erlaubt die vorgefertigten Methoden aus Pytorch zu benutzen.


    1. Bernoulli-Verteilung a.k.a. Münzwurf

      \[ X \sim \text{Ber}_p \Leftrightarrow P(X = 0) = p, \text{ und } P(X=1) = (1-p) \]

      Mit anderen Worten, die Ausgabe soll 1 sein mit einer Wahrscheinlichkeit von \(p\) und 0 mit einer Wahrscheinlichkeit von \((1-p)\).

      Hinweis: Die Funktion torch.rand würfelt die eine zufällige Zahl zwischen 0 und 1. Hinweis: Verwenden Sie die < oder >- Operation auf einem Zufallstensor und nutzen Sie torch.where, um eine 1 oder eine 0 auszugeben.
    2. Binomial-Verteilung a.k.a. \(n\)-facher Münzwurf

      \[ X \sim \text{B}_{n,p} \Leftrightarrow P(X = k) = \binom{n}{k} p^k (1-p)^{n-k} \]

      Die Zufallsvariable ist die Anzahl an 1en, bei einem \(n\)-fachen Münzwurf.
    3. Beliebige Verteilungen mit invertierbaren Verteilungsfunktionen: Wie wir in der nächsten VL sehen (werden), konvergiert die Summe unabhängiger gleichverteilter Zufallsvariablen (z.B. die Summe sehr vieler Münzwürfe) gegen eine Gauss-Verteilung.

      \[ X \sim \mathcal{N}_{\mu,\sigma} \Leftrightarrow P(X = x) = \frac{1}{\sigma \sqrt{2\pi} } e^{-\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)} \]

      Also die oben bestimmte Bernoulli-Verteilte Zufallsvariable zu verwenden, um eine Gauss-Verteilung a.k.a. natürliches Rauschen wäre durchaus machbar, aber leider zum einen ineffektiv, zum anderen Beschränkt auf Gaußverteilungen. Aus diesem Grund möchten wir eine viel allgemeinere Methode implementieren.

      Wir implementieren nun eine Methode, die uns für eine beliebige invertierbare Verteilungsfunktion (CDF) und gegebenes uniformes Rauschen auf dem Interval \([0,1)\) eine reelle Zufallsvariable mit genau der gewünschten Verteilung ausgibt.

      Methode: Gegeben Sei die gewünschten Verteilung, die die Zufallsvariable am Ende haben soll. O.b.d.A. ist ihr Bild \([0,1)\), d.h. sie gibt lediglich Werte zwischen \(0\) und \(1\) aus. Nun samplen wir eine uniform verteilte Zufallsvariable \(X\in[0,1)\). Anschließend schießen wir zufällig von der y-Achse vom gesampleten wert aus in Richtung der CDF (parallel zur x-Achse). Der zugehörige Wert auf der x-Achse zum Schnittpunkt der Geraden mit der CDF definieren wir als Wert unserer Zufallsvariable.

      Implementieren Sie das angegebene Verfahren. Implementieren Sie auch die Reskalierung für den Fall, dass das Bild der Verteilungsfunktion \([a,b)\) ist. Implementieren Sie so eine Gaussverteilte Zufallsvariable. Prüfen Sie auch hier, dass die resultierende Zufallsvariable tatsächlich Gaußverteilt ist.
  2. Zeigen Sie für jede Funktion auch anhand eines Histograms, dass die Verteilung jeweils korrekt ist.

    Hinweis: Am einfachsten ist vermutlich die Verwendung der Funktion torch.histogram, es gibt hier aber auch andere Möglichkeiten.