1import torch
2import torch.nn as nn
3from torchvision import transforms, datasets
4from tqdm import tqdm
5
6LR = 0.01 # Lernrate
7DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
8
9# TODO: Data Augmentation Pipeline
10# Hier definieren wir die Transformationspipeline für die Trainings- und Validierungsdaten.
11# Die Trainingsdaten werden mit verschiedenen Transformationen augmentiert, um die Robustheit des Modells
12# zu erhöhen. Folgend Sie den Anweisungen in der Aufgabenstellung, um die Pipeline zu vervollständigen.
13training_transform = transforms.Compose(
14 [
15 transforms.ToTensor(),
16 transforms.RandomHorizontalFlip(p=0.5),
17 transforms.RandomRotation(degrees=15),
18 transforms.RandomCrop(size=(32, 32), padding=4),
19 transforms.Normalize((0.5,), (0.5,)),
20 ]
21)
22
23# TODO: Validation Pipeline
24# Definieren Sie hier eine zweite Transformationspipeline für die Validierungsdaten.
25# Folgend Sie den Anweisungen in der Aufgabenstellung, um die Pipeline zu vervollständigen.
26validation_transform = transforms.Compose(
27 [transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]
28)
29
30if __name__ == "__main__":
31 # TODO: Laden der CIFAR-100-Daten
32 training_data = datasets.CIFAR100(
33 root="./data", train=True, download=True, transform=training_transform
34 )
35 validation_data = datasets.CIFAR100(
36 root="./data", train=False, download=True, transform=validation_transform
37 )
38
39 training_set = torch.utils.data.DataLoader(
40 training_data, batch_size=256, shuffle=True
41 )
42 validation_set = torch.utils.data.DataLoader(
43 validation_data, batch_size=256, shuffle=False
44 )
45
46
47class CNNNetwork(nn.Module):
48 """Ein einfaches neuronales Netzwerk mit einer versteckten Schicht."""
49
50 def __init__(self):
51 """Initialisiert das Netzwerk mit mehreren Convolutional-Schichten und voll verbundenen Schichten.
52
53 **TODO**:
54
55 - Rufen Sie die Methode `super().__init__()` auf, um die Basisklasse zu initialisieren.
56
57 - Definieren Sie die Faltungs-Schichten `conv1`, `conv2`, `conv3` mit den entsprechenden Eingangs- und Ausgangskanälen. Verwenden Sie `nn.Conv2d(...) <https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html>`_. Setzen Sie `kernel_size=3` und `padding="same"` für alle Schichten.
58 Verwenden Sie jeweils 16, 32 und 64 Ausgänge für `conv1`, `conv2` und `conv3`.
59
60 - Definieren Sie die voll verbundenen Schichten `fc1` und `fc2` mit den entsprechenden Eingangs- und Ausgangsgrößen. Verwenden Sie `nn.Linear(...) <https://pytorch.org/docs/stable/generated/torch.nn.Linear.html>`_. Setzen Sie `fc1` auf 512 Ausgänge und `fc2` auf 100 Ausgänge.
61
62 - Fügen Sie eine `Flatten-Schicht <https://pytorch.org/docs/stable/generated/torch.nn.Flatten.html>`_ hinzu, um die Ausgabe der Convolutional-Schichten in einen Vektor umzuwandeln.
63
64 - Fügen Sie eine `Max-Pooling-Schicht <https://pytorch.org/docs/stable/generated/torch.nn.MaxPool2d.html>`_ `pool` mit `kernel_size=2` und `stride=2` hinzu, um die räumliche Dimension der Feature-Maps zu reduzieren.
65
66 - Verwenden Sie `torch.relu <https://pytorch.org/docs/stable/generated/torch.relu.html>`_ für die Aktivierung.
67 """
68 super().__init__()
69 self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding="same")
70 self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding="same")
71 self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding="same")
72
73 self.bn1 = nn.BatchNorm2d(16)
74 self.bn2 = nn.BatchNorm2d(32)
75 self.bn3 = nn.BatchNorm2d(64)
76
77 self.fc1 = nn.Linear(64 * 4 * 4, 512)
78 self.fc2 = nn.Linear(512, 100)
79 self.flatten = nn.Flatten()
80 self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
81 self.dropout = nn.Dropout(0.5)
82
83 def forward(self, x):
84 """Führt den Vorwärtsdurchlauf des Netzwerks aus.
85
86 **TODO**:
87
88 - Wenden Sie abwechselnd immer die Faltungs-Schichten `conv1`, `conv2`, `conv3` auf die Eingabe `x` an, gefolgt von einer ReLU-Aktivierung und einem Pooling-Layer.
89
90 - Flatten Sie die Ausgabe der letzten Faltungs-Schicht mit .`self.flatten(x)`
91
92 - Wenden Sie die voll verbundenen Schichten `fc1` und `fc2` auf die flachgelegte Ausgabe an, wobei Sie ReLU-Aktivierung auf die Ausgabe von `fc1` anwenden.
93
94 - Geben Sie die Ausgabe der letzten Schicht `fc2` zurück.
95 """
96 x = self.pool(self.bn1(torch.relu(self.conv1(x))))
97 x = self.pool(self.bn2(torch.relu(self.conv2(x))))
98 x = self.pool(self.bn3(torch.relu(self.conv3(x))))
99 x = self.flatten(x)
100 x = torch.relu(self.fc1(x))
101 x = self.dropout(x)
102 x = self.fc2(x)
103 return x
104
105
106def epoch(model, n, train, dataloader, criterion, optimizer):
107 """
108 Führt eine einzelne Trainings- oder Evaluations-Epoche für das Modell aus.
109
110 Parameters:
111 -----------
112
113 model (nn.Module):
114 Das zu trainierende oder evaluierende Modell.
115
116 n (int):
117 Die aktuelle Epoche.
118
119 train (bool):
120 Gibt an, ob die Epoche im Trainingsmodus oder im Evaluationsmodus durchgeführt wird.
121
122 dataloader (DataLoader):
123 Der DataLoader, der die Daten für die Epoche bereitstellt.
124
125 criterion (nn.Module):
126 Das Loss-Kriterium, das zur Berechnung des Verlusts verwendet wird.
127
128 optimizer (torch.optim.Optimizer):
129 Der Optimierer, der zur Aktualisierung der Modellparameter verwendet wird.
130
131 **TODO**:
132
133 - Setzen Sie das Modell in den Trainingsmodus, wenn `train=True` ist, und in den Evaluationsmodus, wenn `train=False` ist. Rufen Sie dazu `model.train()` bzw. `model.eval()` auf.
134
135 - Initialisieren Sie `total_loss`, `total_samples` und `total_correct` auf 0.0, 0 und 0.
136
137 - Verwenden Sie `tqdm` für den Fortschrittsbalken, um den Fortschritt der Epoche anzuzeigen.
138 Speichern Sie den Iterator in einer eigenen Variable damit er innerhalb der Schleife verwendet werden kann.
139
140 - Iterieren Sie über den `dataloader` und führen Sie die folgenden Schritte aus:
141
142 - Verschieben Sie die Daten und Labels auf das Gerät (`DEVICE`).
143
144 - Setzen Sie die Gradienten des Optimierers zurück, wenn `train=True` ist, indem Sie `optimizer.zero_grad()` aufrufen.
145
146 - Führen Sie den Vorwärtsdurchlauf des Modells aus, indem Sie `model(data)` aufrufen. Verwenden Sie `torch.set_grad_enabled(train)`, um den Gradientenfluss nur im Trainingsmodus zu aktivieren.
147
148 - Berechnen Sie den Verlust mit `criterion(outputs, labels)`.
149
150 - Wenn `train=True` ist, führen Sie den Rückwärtsdurchlauf aus, indem Sie `loss.backward()` aufrufen und die Parameter mit `optimizer.step()` aktualisieren.
151
152 - Aktualisieren Sie `total_loss`, `total_samples` und `total_correct` mit den entsprechenden Werten aus dem aktuellen Batch.
153 Der `total_loss` sollte den Verlust des aktuellen Batches aufsummieren, `total_samples` die Anzahl der Samples im aktuellen Batch und
154 `total_correct` die Anzahl der korrekt klassifizierten Samples. Die Anzahl der korrekt klassifizierten Samples kann mit `(outputs.argmax(dim=1) == labels).sum()` berechnet werden.
155
156 - Aktualisieren Sie den Fortschrittsbalken mit dem aktuellen Verlust und der Genauigkeit. Zeigen Sie auch an ob das Netz im Trainings- oder Validationsmodus betrieben wird.
157 Rufen Sie dazu tqdm.set_description() auf und formatieren Sie die Ausgabe entsprechend.
158 """
159 # Vorbereiten des Modells für Training oder Evaluation
160 if train:
161 model.train()
162 else:
163 model.eval()
164
165 # Training des Modells
166 total_loss = 0.0
167 total_samples = 0
168 total_correct = 0
169 bar = tqdm(dataloader)
170 for data, labels in bar:
171 # Daten und Labels auf das Gerät verschieben
172 data, labels = data.to(DEVICE), labels.to(DEVICE)
173
174 # Gradienten zurücksetzen
175 if train:
176 optimizer.zero_grad()
177
178 # Vorwärtsdurchlauf
179 with torch.set_grad_enabled(train):
180 outputs = model(data)
181
182 # Verlust berechnen und Rückwärtsdurchlauf
183 loss = criterion(outputs, labels)
184
185 # Gradienten berechnen
186 if train:
187 loss.backward()
188 optimizer.step()
189
190 # Aktualisieren der Metriken
191 total_loss += loss.item()
192 total_samples += data.size(0)
193 total_correct += (outputs.argmax(dim=1) == labels).sum().item()
194
195 bar.set_description(
196 f"Epoch {n} ({'T' if train else 'V'}), Loss: {total_loss / total_samples:.4f}, Accuracy: {total_correct / total_samples:.2%}"
197 )
198
199
200if __name__ == "__main__":
201 # Initialisierung des Modells, Loss-Kriteriums und Optimierers
202 model = CNNNetwork().to(DEVICE)
203 criterion = nn.CrossEntropyLoss()
204 optimizer = torch.optim.Adam(model.parameters(), lr=LR)
205
206 # Das Modell trainieren
207 model.train()
208 for n in range(1, 30):
209 epoch(model, n, True, training_set, criterion, optimizer)
210 epoch(model, n, False, validation_set, criterion, optimizer)