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

  /  





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:
      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.