mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
421 lines
32 KiB
Markdown
421 lines
32 KiB
Markdown
# Deep Learning
|
||
|
||
{{#include ../banners/hacktricks-training.md}}
|
||
|
||
## Deep Learning
|
||
|
||
Uczenie głębokie to podzbiór uczenia maszynowego, który wykorzystuje sieci neuronowe z wieloma warstwami (głębokie sieci neuronowe) do modelowania złożonych wzorców w danych. Osiągnęło ono niezwykły sukces w różnych dziedzinach, w tym w wizji komputerowej, przetwarzaniu języka naturalnego i rozpoznawaniu mowy.
|
||
|
||
### Neural Networks
|
||
|
||
Sieci neuronowe są podstawowymi elementami uczenia głębokiego. Składają się z połączonych węzłów (neuronów) zorganizowanych w warstwy. Każdy neuron otrzymuje dane wejściowe, stosuje ważoną sumę i przekazuje wynik przez funkcję aktywacji, aby uzyskać wyjście. Warstwy można sklasyfikować w następujący sposób:
|
||
- **Input Layer**: Pierwsza warstwa, która otrzymuje dane wejściowe.
|
||
- **Hidden Layers**: Warstwy pośrednie, które wykonują transformacje na danych wejściowych. Liczba warstw ukrytych i neuronów w każdej warstwie może się różnić, co prowadzi do różnych architektur.
|
||
- **Output Layer**: Ostatnia warstwa, która produkuje wyjście sieci, takie jak prawdopodobieństwa klas w zadaniach klasyfikacyjnych.
|
||
|
||
### Activation Functions
|
||
|
||
Gdy warstwa neuronów przetwarza dane wejściowe, każdy neuron stosuje wagę i bias do wejścia (`z = w * x + b`), gdzie `w` to waga, `x` to wejście, a `b` to bias. Wyjście neuronu jest następnie przekazywane przez **funkcję aktywacji, aby wprowadzić nieliniowość** do modelu. Ta funkcja aktywacji zasadniczo wskazuje, czy następny neuron "powinien być aktywowany i w jakim stopniu". Umożliwia to sieci uczenie się złożonych wzorców i relacji w danych, co pozwala jej przybliżać dowolną funkcję ciągłą.
|
||
|
||
Dlatego funkcje aktywacji wprowadzają nieliniowość do sieci neuronowej, umożliwiając jej uczenie się złożonych relacji w danych. Powszechne funkcje aktywacji to:
|
||
- **Sigmoid**: Mapuje wartości wejściowe na zakres między 0 a 1, często używane w klasyfikacji binarnej.
|
||
- **ReLU (Rectified Linear Unit)**: Zwraca bezpośrednio wejście, jeśli jest dodatnie; w przeciwnym razie zwraca zero. Jest szeroko stosowane ze względu na swoją prostotę i skuteczność w trenowaniu głębokich sieci.
|
||
- **Tanh**: Mapuje wartości wejściowe na zakres między -1 a 1, często używane w warstwach ukrytych.
|
||
- **Softmax**: Przekształca surowe wyniki w prawdopodobieństwa, często używane w warstwie wyjściowej do klasyfikacji wieloklasowej.
|
||
|
||
### Backpropagation
|
||
|
||
Backpropagation to algorytm używany do trenowania sieci neuronowych poprzez dostosowywanie wag połączeń między neuronami. Działa poprzez obliczanie gradientu funkcji straty względem każdej wagi i aktualizowanie wag w przeciwnym kierunku gradientu, aby zminimalizować stratę. Kroki zaangażowane w backpropagation to:
|
||
|
||
1. **Forward Pass**: Oblicz wyjście sieci, przekazując dane wejściowe przez warstwy i stosując funkcje aktywacji.
|
||
2. **Loss Calculation**: Oblicz stratę (błąd) między przewidywanym wyjściem a prawdziwym celem za pomocą funkcji straty (np. średni błąd kwadratowy dla regresji, entropia krzyżowa dla klasyfikacji).
|
||
3. **Backward Pass**: Oblicz gradienty straty względem każdej wagi, korzystając z reguły łańcuchowej rachunku różniczkowego.
|
||
4. **Weight Update**: Zaktualizuj wagi, korzystając z algorytmu optymalizacji (np. stochastyczny spadek gradientu, Adam), aby zminimalizować stratę.
|
||
|
||
## Convolutional Neural Networks (CNNs)
|
||
|
||
Konwolucyjne sieci neuronowe (CNN) to specjalizowany typ sieci neuronowej zaprojektowany do przetwarzania danych w formie siatki, takich jak obrazy. Są szczególnie skuteczne w zadaniach związanych z wizją komputerową dzięki swojej zdolności do automatycznego uczenia się przestrzennych hierarchii cech.
|
||
|
||
Główne komponenty CNN to:
|
||
- **Convolutional Layers**: Stosują operacje konwolucji do danych wejściowych, używając uczących się filtrów (jąder) do wydobywania lokalnych cech. Każdy filtr przesuwa się po wejściu i oblicza iloczyn skalarny, produkując mapę cech.
|
||
- **Pooling Layers**: Zmniejszają rozmiary map cech, zachowując ważne cechy. Powszechne operacje poolingowe to max pooling i average pooling.
|
||
- **Fully Connected Layers**: Łączą każdy neuron w jednej warstwie z każdym neuronem w następnej warstwie, podobnie jak w tradycyjnych sieciach neuronowych. Te warstwy są zazwyczaj używane na końcu sieci do zadań klasyfikacyjnych.
|
||
|
||
Wewnątrz CNN **`Convolutional Layers`**, możemy również wyróżnić:
|
||
- **Initial Convolutional Layer**: Pierwsza warstwa konwolucyjna, która przetwarza surowe dane wejściowe (np. obraz) i jest przydatna do identyfikacji podstawowych cech, takich jak krawędzie i tekstury.
|
||
- **Intermediate Convolutional Layers**: Kolejne warstwy konwolucyjne, które budują na cechach wyuczonych przez warstwę początkową, pozwalając sieci na uczenie się bardziej złożonych wzorców i reprezentacji.
|
||
- **Final Convolutional Layer**: Ostatnie warstwy konwolucyjne przed warstwami w pełni połączonymi, które uchwycają cechy na wysokim poziomie i przygotowują dane do klasyfikacji.
|
||
|
||
> [!TIP]
|
||
> CNN są szczególnie skuteczne w klasyfikacji obrazów, detekcji obiektów i zadaniach segmentacji obrazów dzięki ich zdolności do uczenia się przestrzennych hierarchii cech w danych w formie siatki oraz redukcji liczby parametrów poprzez dzielenie wag.
|
||
> Co więcej, działają lepiej z danymi wspierającymi zasadę lokalności cech, gdzie sąsiednie dane (piksele) są bardziej prawdopodobne, że są ze sobą powiązane niż odległe piksele, co może nie mieć miejsca w przypadku innych typów danych, takich jak tekst.
|
||
> Ponadto, zauważ, jak CNN będą w stanie identyfikować nawet złożone cechy, ale nie będą w stanie zastosować żadnego kontekstu przestrzennego, co oznacza, że ta sama cecha znaleziona w różnych częściach obrazu będzie taka sama.
|
||
|
||
### Example defining a CNN
|
||
|
||
*Tutaj znajdziesz opis, jak zdefiniować konwolucyjną sieć neuronową (CNN) w PyTorch, która zaczyna się od partii obrazów RGB jako zbioru danych o rozmiarze 48x48 i wykorzystuje warstwy konwolucyjne oraz maxpool do wydobywania cech, a następnie warstwy w pełni połączone do klasyfikacji.*
|
||
|
||
Tak można zdefiniować 1 warstwę konwolucyjną w PyTorch: `self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1)`.
|
||
|
||
- `in_channels`: Liczba kanałów wejściowych. W przypadku obrazów RGB jest to 3 (jeden dla każdego kanału kolorystycznego). Jeśli pracujesz z obrazami w odcieniach szarości, będzie to 1.
|
||
|
||
- `out_channels`: Liczba kanałów wyjściowych (filtrów), które warstwa konwolucyjna będzie uczyć. To jest hiperparametr, który możesz dostosować w zależności od architektury swojego modelu.
|
||
|
||
- `kernel_size`: Rozmiar filtra konwolucyjnego. Powszechnym wyborem jest 3x3, co oznacza, że filtr pokryje obszar 3x3 obrazu wejściowego. To jak stempel kolorowy 3×3×3, który jest używany do generowania out_channels z in_channels:
|
||
1. Umieść ten stempel 3×3×3 w lewym górnym rogu sześcianu obrazu.
|
||
2. Pomnóż każdą wagę przez piksel pod nim, dodaj je wszystkie, dodaj bias → otrzymujesz jedną liczbę.
|
||
3. Zapisz tę liczbę na pustej mapie w pozycji (0, 0).
|
||
4. Przesuń stempel o jeden piksel w prawo (stride = 1) i powtórz, aż wypełnisz całą siatkę 48×48.
|
||
|
||
- `padding`: Liczba pikseli dodawanych do każdej strony wejścia. Padding pomaga zachować wymiary przestrzenne wejścia, co pozwala na większą kontrolę nad rozmiarem wyjścia. Na przykład, przy jądrze 3x3 i wejściu o rozmiarze 48x48, padding równy 1 zachowa ten sam rozmiar wyjścia (48x48) po operacji konwolucji. Dzieje się tak, ponieważ padding dodaje obramowanie o 1 pikselu wokół obrazu wejściowego, co pozwala jądrowi przesuwać się po krawędziach bez zmniejszania wymiarów przestrzennych.
|
||
|
||
Wówczas liczba parametrów do wytrenowania w tej warstwie wynosi:
|
||
- (3x3x3 (rozmiar jądra) + 1 (bias)) x 32 (out_channels) = 896 parametrów do wytrenowania.
|
||
|
||
Zauważ, że do każdego używanego jądra dodawany jest bias (+1), ponieważ funkcją każdej warstwy konwolucyjnej jest nauczenie się liniowej transformacji wejścia, co jest reprezentowane przez równanie:
|
||
```plaintext
|
||
Y = f(W * X + b)
|
||
```
|
||
gdzie `W` to macierz wag (nauczone filtry, 3x3x3 = 27 parametrów), `b` to wektor biasu, który wynosi +1 dla każdego kanału wyjściowego.
|
||
|
||
Zauważ, że wyjście `self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1)` będzie tensor o kształcie `(batch_size, 32, 48, 48)`, ponieważ 32 to nowa liczba generowanych kanałów o rozmiarze 48x48 pikseli.
|
||
|
||
Następnie możemy połączyć tę warstwę konwolucyjną z inną warstwą konwolucyjną, jak: `self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)`.
|
||
|
||
Co doda: (32x3x3 (rozmiar jądra) + 1 (bias)) x 64 (out_channels) = 18,496 parametrów do wytrenowania i wyjście o kształcie `(batch_size, 64, 48, 48)`.
|
||
|
||
Jak widać, **liczba parametrów szybko rośnie z każdą dodatkową warstwą konwolucyjną**, szczególnie w miarę zwiększania liczby kanałów wyjściowych.
|
||
|
||
Jedną z opcji kontrolowania ilości używanych danych jest zastosowanie **max pooling** po każdej warstwie konwolucyjnej. Max pooling redukuje wymiary przestrzenne map cech, co pomaga zmniejszyć liczbę parametrów i złożoność obliczeniową, jednocześnie zachowując ważne cechy.
|
||
|
||
Można to zadeklarować jako: `self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)`. To zasadniczo wskazuje na użycie siatki 2x2 pikseli i pobranie maksymalnej wartości z każdej siatki, aby zmniejszyć rozmiar mapy cech o połowę. Ponadto `stride=2` oznacza, że operacja poolingowa będzie przesuwać się o 2 piksele na raz, w tym przypadku zapobiegając jakimkolwiek nakładkom między obszarami poolingowymi.
|
||
|
||
Z tą warstwą poolingową, kształt wyjścia po pierwszej warstwie konwolucyjnej wynosiłby `(batch_size, 64, 24, 24)` po zastosowaniu `self.pool1` do wyjścia `self.conv2`, zmniejszając rozmiar do 1/4 poprzedniej warstwy.
|
||
|
||
> [!TIP]
|
||
> Ważne jest, aby stosować pooling po warstwach konwolucyjnych, aby zmniejszyć wymiary przestrzenne map cech, co pomaga kontrolować liczbę parametrów i złożoność obliczeniową, jednocześnie sprawiając, że początkowy parametr uczy się ważnych cech.
|
||
> Możesz postrzegać konwolucje przed warstwą poolingową jako sposób na wydobycie cech z danych wejściowych (jak linie, krawędzie), ta informacja nadal będzie obecna w wyjściu po pooling, ale następna warstwa konwolucyjna nie będzie mogła zobaczyć oryginalnych danych wejściowych, tylko wyjście po pooling, które jest zredukowaną wersją poprzedniej warstwy z tą informacją.
|
||
> W zwykłej kolejności: `Conv → ReLU → Pool` każde okno poolingowe 2×2 teraz konkurowało z aktywacjami cech (“krawędź obecna / nie”), a nie surowymi intensywnościami pikseli. Utrzymanie najsilniejszej aktywacji naprawdę zachowuje najbardziej istotne dowody.
|
||
|
||
Następnie, po dodaniu tylu warstw konwolucyjnych i poolingowych, ile to konieczne, możemy spłaszczyć wyjście, aby wprowadzić je do w pełni połączonych warstw. Robi się to przez przekształcenie tensora w wektor 1D dla każdej próbki w partii:
|
||
```python
|
||
x = x.view(-1, 64*24*24)
|
||
```
|
||
A z tym wektorem 1D zawierającym wszystkie parametry treningowe wygenerowane przez poprzednie warstwy konwolucyjne i poolingowe, możemy zdefiniować warstwę w pełni połączoną w następujący sposób:
|
||
```python
|
||
self.fc1 = nn.Linear(64 * 24 * 24, 512)
|
||
```
|
||
Który weźmie spłaszczone wyjście z poprzedniej warstwy i odwzoruje je na 512 ukrytych jednostek.
|
||
|
||
Zauważ, że ta warstwa dodała `(64 * 24 * 24 + 1 (bias)) * 512 = 3,221,504` trenowalnych parametrów, co stanowi znaczący wzrost w porównaniu do warstw konwolucyjnych. Dzieje się tak, ponieważ warstwy w pełni połączone łączą każdy neuron w jednej warstwie z każdym neuronem w następnej warstwie, co prowadzi do dużej liczby parametrów.
|
||
|
||
Na koniec możemy dodać warstwę wyjściową, aby wygenerować ostateczne logity klas:
|
||
```python
|
||
self.fc2 = nn.Linear(512, num_classes)
|
||
```
|
||
To doda `(512 + 1 (bias)) * num_classes` parametry do uczenia, gdzie `num_classes` to liczba klas w zadaniu klasyfikacji (np. 43 dla zestawu danych GTSRB).
|
||
|
||
Jedną z ostatnich powszechnych praktyk jest dodanie warstwy dropout przed w pełni połączonymi warstwami, aby zapobiec przeuczeniu. Można to zrobić za pomocą:
|
||
```python
|
||
self.dropout = nn.Dropout(0.5)
|
||
```
|
||
Ta warstwa losowo ustawia ułamek jednostek wejściowych na zero podczas treningu, co pomaga zapobiegać przeuczeniu, zmniejszając zależność od konkretnych neuronów.
|
||
|
||
### Przykład kodu CNN
|
||
```python
|
||
import torch
|
||
import torch.nn as nn
|
||
import torch.nn.functional as F
|
||
|
||
class MY_NET(nn.Module):
|
||
def __init__(self, num_classes=32):
|
||
super(MY_NET, self).__init__()
|
||
# Initial conv layer: 3 input channels (RGB), 32 output channels, 3x3 kernel, padding 1
|
||
# This layer will learn basic features like edges and textures
|
||
self.conv1 = nn.Conv2d(
|
||
in_channels=3, out_channels=32, kernel_size=3, padding=1
|
||
)
|
||
# Output: (Batch Size, 32, 48, 48)
|
||
|
||
# Conv Layer 2: 32 input channels, 64 output channels, 3x3 kernel, padding 1
|
||
# This layer will learn more complex features based on the output of conv1
|
||
self.conv2 = nn.Conv2d(
|
||
in_channels=32, out_channels=64, kernel_size=3, padding=1
|
||
)
|
||
# Output: (Batch Size, 64, 48, 48)
|
||
|
||
# Max Pooling 1: Kernel 2x2, Stride 2. Reduces spatial dimensions by half (1/4th of the previous layer).
|
||
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
|
||
# Output: (Batch Size, 64, 24, 24)
|
||
|
||
# Conv Layer 3: 64 input channels, 128 output channels, 3x3 kernel, padding 1
|
||
# This layer will learn even more complex features based on the output of conv2
|
||
# Note that the number of output channels can be adjusted based on the complexity of the task
|
||
self.conv3 = nn.Conv2d(
|
||
in_channels=64, out_channels=128, kernel_size=3, padding=1
|
||
)
|
||
# Output: (Batch Size, 128, 24, 24)
|
||
|
||
# Max Pooling 2: Kernel 2x2, Stride 2. Reduces spatial dimensions by half again.
|
||
# Reducing the dimensions further helps to control the number of parameters and computational complexity.
|
||
self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
|
||
# Output: (Batch Size, 128, 12, 12)
|
||
|
||
# From the second pooling layer, we will flatten the output to feed it into fully connected layers.
|
||
# The feature size is calculated as follows:
|
||
# Feature size = Number of output channels * Height * Width
|
||
self._feature_size = 128 * 12 * 12
|
||
|
||
# Fully Connected Layer 1 (Hidden): Maps flattened features to hidden units.
|
||
# This layer will learn to combine the features extracted by the convolutional layers.
|
||
self.fc1 = nn.Linear(self._feature_size, 512)
|
||
|
||
# Fully Connected Layer 2 (Output): Maps hidden units to class logits.
|
||
# Output size MUST match num_classes
|
||
self.fc2 = nn.Linear(512, num_classes)
|
||
|
||
# Dropout layer configuration with a dropout rate of 0.5.
|
||
# This layer is used to prevent overfitting by randomly setting a fraction of the input units to zero during training.
|
||
self.dropout = nn.Dropout(0.5)
|
||
|
||
def forward(self, x):
|
||
"""
|
||
The forward method defines the forward pass of the network.
|
||
It takes an input tensor `x` and applies the convolutional layers, pooling layers, and fully connected layers in sequence.
|
||
The input tensor `x` is expected to have the shape (Batch Size, Channels, Height, Width), where:
|
||
- Batch Size: Number of samples in the batch
|
||
- Channels: Number of input channels (e.g., 3 for RGB images)
|
||
- Height: Height of the input image (e.g., 48 for 48x48 images)
|
||
- Width: Width of the input image (e.g., 48 for 48x48 images)
|
||
The output of the forward method is the logits for each class, which can be used for classification tasks.
|
||
Args:
|
||
x (torch.Tensor): Input tensor of shape (Batch Size, Channels, Height, Width)
|
||
Returns:
|
||
torch.Tensor: Output tensor of shape (Batch Size, num_classes) containing the class logits.
|
||
"""
|
||
|
||
# Conv1 -> ReLU -> Conv2 -> ReLU -> Pool1 -> Conv3 -> ReLU -> Pool2
|
||
x = self.conv1(x)
|
||
x = F.relu(x)
|
||
x = self.conv2(x)
|
||
x = F.relu(x)
|
||
x = self.pool1(x)
|
||
x = self.conv3(x)
|
||
x = F.relu(x)
|
||
x = self.pool2(x)
|
||
# At this point, x has shape (Batch Size, 128, 12, 12)
|
||
|
||
# Flatten the output to feed it into fully connected layers
|
||
x = torch.flatten(x, 1)
|
||
|
||
# Apply dropout to prevent overfitting
|
||
x = self.dropout(x)
|
||
|
||
# First FC layer with ReLU activation
|
||
x = F.relu(self.fc1(x))
|
||
|
||
# Apply Dropout again
|
||
x = self.dropout(x)
|
||
# Final FC layer to get logits
|
||
x = self.fc2(x)
|
||
# Output shape will be (Batch Size, num_classes)
|
||
# Note that the output is not passed through a softmax activation here, as it is typically done in the loss function (e.g., CrossEntropyLoss)
|
||
return x
|
||
```
|
||
### Przykład kodu treningowego CNN
|
||
|
||
Poniższy kod stworzy dane treningowe i wytrenuje model `MY_NET` zdefiniowany powyżej. Oto kilka interesujących wartości do zauważenia:
|
||
|
||
- `EPOCHS` to liczba razy, kiedy model zobaczy cały zbiór danych podczas treningu. Jeśli EPOCH jest zbyt mały, model może nie nauczyć się wystarczająco; jeśli zbyt duży, może przeuczyć się.
|
||
- `LEARNING_RATE` to rozmiar kroku dla optymalizatora. Mała wartość learning rate może prowadzić do wolnej konwergencji, podczas gdy duża może przekroczyć optymalne rozwiązanie i uniemożliwić konwergencję.
|
||
- `WEIGHT_DECAY` to termin regularizacji, który pomaga zapobiegać przeuczeniu poprzez karanie dużych wag.
|
||
|
||
Jeśli chodzi o pętlę treningową, oto kilka interesujących informacji do poznania:
|
||
- `criterion = nn.CrossEntropyLoss()` to funkcja straty używana do zadań klasyfikacji wieloklasowej. Łączy aktywację softmax i stratę krzyżową w jednej funkcji, co czyni ją odpowiednią do trenowania modeli, które zwracają logity klas.
|
||
- Jeśli model miałby zwracać inne typy wyjść, takie jak klasyfikacja binarna lub regresja, używalibyśmy różnych funkcji straty, takich jak `nn.BCEWithLogitsLoss()` dla klasyfikacji binarnej lub `nn.MSELoss()` dla regresji.
|
||
- `optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE, weight_decay=WEIGHT_DECAY)` inicjalizuje optymalizator Adam, który jest popularnym wyborem do trenowania modeli głębokiego uczenia. Dostosowuje on learning rate dla każdego parametru na podstawie pierwszych i drugich momentów gradientów.
|
||
- Inne optymalizatory, takie jak `optim.SGD` (Stochastic Gradient Descent) lub `optim.RMSprop`, mogą być również używane, w zależności od specyficznych wymagań zadania treningowego.
|
||
- Metoda `model.train()` ustawia model w tryb treningowy, umożliwiając warstwom takim jak dropout i normalizacja wsadowa zachowanie się inaczej podczas treningu w porównaniu do ewaluacji.
|
||
- `optimizer.zero_grad()` czyści gradienty wszystkich optymalizowanych tensorów przed przejściem wstecznym, co jest konieczne, ponieważ gradienty domyślnie kumulują się w PyTorch. Jeśli nie zostaną wyczyszczone, gradienty z poprzednich iteracji byłyby dodawane do bieżących gradientów, co prowadziłoby do niepoprawnych aktualizacji.
|
||
- `loss.backward()` oblicza gradienty straty względem parametrów modelu, które są następnie używane przez optymalizator do aktualizacji wag.
|
||
- `optimizer.step()` aktualizuje parametry modelu na podstawie obliczonych gradientów i learning rate.
|
||
```python
|
||
import torch, torch.nn.functional as F
|
||
from torch import nn, optim
|
||
from torch.utils.data import DataLoader
|
||
from torchvision import datasets, transforms
|
||
from tqdm import tqdm
|
||
from sklearn.metrics import classification_report, confusion_matrix
|
||
import numpy as np
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 1. Globals
|
||
# ---------------------------------------------------------------------------
|
||
IMG_SIZE = 48 # model expects 48×48
|
||
NUM_CLASSES = 10 # MNIST has 10 digits
|
||
BATCH_SIZE = 64 # batch size for training and validation
|
||
EPOCHS = 5 # number of training epochs
|
||
LEARNING_RATE = 1e-3 # initial learning rate for Adam optimiser
|
||
WEIGHT_DECAY = 1e-4 # L2 regularisation to prevent overfitting
|
||
|
||
# Channel-wise mean / std for MNIST (grayscale ⇒ repeat for 3-channel input)
|
||
MNIST_MEAN = (0.1307, 0.1307, 0.1307)
|
||
MNIST_STD = (0.3081, 0.3081, 0.3081)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 2. Transforms
|
||
# ---------------------------------------------------------------------------
|
||
# 1) Baseline transform: resize + tensor (no colour/aug/no normalise)
|
||
transform_base = transforms.Compose([
|
||
transforms.Resize((IMG_SIZE, IMG_SIZE)), # 🔹 Resize – force all images to 48 × 48 so the CNN sees a fixed geometry
|
||
transforms.Grayscale(num_output_channels=3), # 🔹 Grayscale→RGB – MNIST is 1-channel; duplicate into 3 channels for convnet
|
||
transforms.ToTensor(), # 🔹 ToTensor – convert PIL image [0‒255] → float tensor [0.0‒1.0]
|
||
])
|
||
|
||
# 2) Training transform: augment + normalise
|
||
transform_norm = transforms.Compose([
|
||
transforms.Resize((IMG_SIZE, IMG_SIZE)), # keep 48 × 48 input size
|
||
transforms.Grayscale(num_output_channels=3), # still need 3 channels
|
||
transforms.RandomRotation(10), # 🔹 RandomRotation(±10°) – small tilt ⇢ rotation-invariance, combats overfitting
|
||
transforms.ColorJitter(brightness=0.2,
|
||
contrast=0.2), # 🔹 ColorJitter – pseudo-RGB brightness/contrast noise; extra variety
|
||
transforms.ToTensor(), # convert to tensor before numeric ops
|
||
transforms.Normalize(mean=MNIST_MEAN,
|
||
std=MNIST_STD), # 🔹 Normalize – zero-centre & scale so every channel ≈ N(0,1)
|
||
])
|
||
|
||
# 3) Test/validation transform: only resize + normalise (no aug)
|
||
transform_test = transforms.Compose([
|
||
transforms.Resize((IMG_SIZE, IMG_SIZE)), # same spatial size as train
|
||
transforms.Grayscale(num_output_channels=3), # match channel count
|
||
transforms.ToTensor(), # tensor conversion
|
||
transforms.Normalize(mean=MNIST_MEAN,
|
||
std=MNIST_STD), # 🔹 keep test data on same scale as training data
|
||
])
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 3. Datasets & loaders
|
||
# ---------------------------------------------------------------------------
|
||
train_set = datasets.MNIST("data", train=True, download=True, transform=transform_norm)
|
||
test_set = datasets.MNIST("data", train=False, download=True, transform=transform_test)
|
||
|
||
train_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True)
|
||
test_loader = DataLoader(test_set, batch_size=256, shuffle=False)
|
||
|
||
print(f"Training on {len(train_set)} samples, validating on {len(test_set)} samples.")
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 4. Model / loss / optimiser
|
||
# ---------------------------------------------------------------------------
|
||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||
model = MY_NET(num_classes=NUM_CLASSES).to(device)
|
||
|
||
criterion = nn.CrossEntropyLoss()
|
||
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE, weight_decay=WEIGHT_DECAY)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 5. Training loop
|
||
# ---------------------------------------------------------------------------
|
||
for epoch in range(1, EPOCHS + 1):
|
||
model.train() # Set model to training mode enabling dropout and batch norm
|
||
|
||
running_loss = 0.0 # sums batch losses to compute epoch average
|
||
correct = 0 # number of correct predictions
|
||
total = 0 # number of samples seen
|
||
|
||
# tqdm wraps the loader to show a live progress-bar per epoch
|
||
for X_batch, y_batch in tqdm(train_loader, desc=f"Epoch {epoch}", leave=False):
|
||
# 3-a) Move data to GPU (if available) ----------------------------------
|
||
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
|
||
|
||
# 3-b) Forward pass -----------------------------------------------------
|
||
logits = model(X_batch) # raw class scores (shape: [B, NUM_CLASSES])
|
||
loss = criterion(logits, y_batch)
|
||
|
||
# 3-c) Backward pass & parameter update --------------------------------
|
||
optimizer.zero_grad() # clear old gradients
|
||
loss.backward() # compute new gradients
|
||
optimizer.step() # gradient → weight update
|
||
|
||
# 3-d) Statistics -------------------------------------------------------
|
||
running_loss += loss.item() * X_batch.size(0) # sum of (batch loss × batch size)
|
||
preds = logits.argmax(dim=1) # predicted class labels
|
||
correct += (preds == y_batch).sum().item() # correct predictions in this batch
|
||
total += y_batch.size(0) # samples processed so far
|
||
|
||
# 3-e) Epoch-level metrics --------------------------------------------------
|
||
epoch_loss = running_loss / total
|
||
epoch_acc = 100.0 * correct / total
|
||
print(f"[Epoch {epoch}] loss = {epoch_loss:.4f} | accuracy = {epoch_acc:.2f}%")
|
||
|
||
print("\n✅ Training finished.\n")
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 6. Evaluation on test set
|
||
# ---------------------------------------------------------------------------
|
||
model.eval() # Set model to evaluation mode (disables dropout and batch norm)
|
||
with torch.no_grad():
|
||
logits_all, labels_all = [], []
|
||
for X, y in test_loader:
|
||
logits_all.append(model(X.to(device)).cpu())
|
||
labels_all.append(y)
|
||
logits_all = torch.cat(logits_all)
|
||
labels_all = torch.cat(labels_all)
|
||
preds_all = logits_all.argmax(1)
|
||
|
||
test_loss = criterion(logits_all, labels_all).item()
|
||
test_acc = (preds_all == labels_all).float().mean().item() * 100
|
||
|
||
print(f"Test loss: {test_loss:.4f}")
|
||
print(f"Test accuracy: {test_acc:.2f}%\n")
|
||
|
||
print("Classification report (precision / recall / F1):")
|
||
print(classification_report(labels_all, preds_all, zero_division=0))
|
||
|
||
print("Confusion matrix (rows = true, cols = pred):")
|
||
print(confusion_matrix(labels_all, preds_all))
|
||
```
|
||
## Sieci Neuronowe Rekurencyjne (RNN)
|
||
|
||
Sieci Neuronowe Rekurencyjne (RNN) to klasa sieci neuronowych zaprojektowanych do przetwarzania danych sekwencyjnych, takich jak szereg czasowy lub język naturalny. W przeciwieństwie do tradycyjnych sieci neuronowych typu feedforward, RNN mają połączenia, które wracają do siebie, co pozwala im utrzymywać ukryty stan, który przechwycuje informacje o poprzednich wejściach w sekwencji.
|
||
|
||
Główne składniki RNN obejmują:
|
||
- **Warstwy Rekurencyjne**: Te warstwy przetwarzają sekwencje wejściowe krok po kroku, aktualizując swój ukryty stan na podstawie bieżącego wejścia i poprzedniego ukrytego stanu. To pozwala RNN uczyć się zależności czasowych w danych.
|
||
- **Ukryty Stan**: Ukryty stan to wektor, który podsumowuje informacje z poprzednich kroków czasowych. Jest aktualizowany w każdym kroku czasowym i jest używany do dokonywania prognoz dla bieżącego wejścia.
|
||
- **Warstwa Wyjściowa**: Warstwa wyjściowa produkuje ostateczne prognozy na podstawie ukrytego stanu. W wielu przypadkach RNN są używane do zadań takich jak modelowanie języka, gdzie wyjście jest rozkładem prawdopodobieństwa dla następnego słowa w sekwencji.
|
||
|
||
Na przykład, w modelu językowym, RNN przetwarza sekwencję słów, na przykład "Kot usiadł na" i przewiduje następne słowo na podstawie kontekstu dostarczonego przez poprzednie słowa, w tym przypadku "macie".
|
||
|
||
### Długoterminowa Pamięć Krótkoterminowa (LSTM) i Gated Recurrent Unit (GRU)
|
||
|
||
RNN są szczególnie skuteczne w zadaniach związanych z danymi sekwencyjnymi, takimi jak modelowanie języka, tłumaczenie maszynowe i rozpoznawanie mowy. Jednak mogą mieć trudności z **długozasięgowymi zależnościami z powodu problemów takich jak znikające gradienty**.
|
||
|
||
Aby to rozwiązać, opracowano specjalistyczne architektury, takie jak Długoterminowa Pamięć Krótkoterminowa (LSTM) i Gated Recurrent Unit (GRU). Te architektury wprowadzają mechanizmy bramkowe, które kontrolują przepływ informacji, co pozwala im skuteczniej uchwycić długozasięgowe zależności.
|
||
|
||
- **LSTM**: Sieci LSTM używają trzech bramek (bramka wejściowa, bramka zapomnienia i bramka wyjściowa) do regulacji przepływu informacji do i z stanu komórki, co umożliwia im zapamiętywanie lub zapominanie informacji w długich sekwencjach. Bramka wejściowa kontroluje, ile nowych informacji dodać na podstawie wejścia i poprzedniego ukrytego stanu, bramka zapomnienia kontroluje, ile informacji odrzucić. Łącząc bramkę wejściową i bramkę zapomnienia, uzyskujemy nowy stan. Na koniec, łącząc nowy stan komórki z wejściem i poprzednim ukrytym stanem, uzyskujemy również nowy ukryty stan.
|
||
- **GRU**: Sieci GRU upraszczają architekturę LSTM, łącząc bramki wejściowe i zapomnienia w jedną bramkę aktualizacji, co czyni je obliczeniowo bardziej wydajnymi, jednocześnie uchwytując długozasięgowe zależności.
|
||
|
||
## LLMs (Duże Modele Językowe)
|
||
|
||
Duże Modele Językowe (LLMs) to rodzaj modelu głębokiego uczenia, zaprojektowanego specjalnie do zadań przetwarzania języka naturalnego. Są trenowane na ogromnych ilościach danych tekstowych i mogą generować tekst przypominający ludzki, odpowiadać na pytania, tłumaczyć języki i wykonywać różne inne zadania związane z językiem.
|
||
LLMs są zazwyczaj oparte na architekturach transformatorowych, które wykorzystują mechanizmy samouważności do uchwycenia relacji między słowami w sekwencji, co pozwala im zrozumieć kontekst i generować spójny tekst.
|
||
|
||
### Architektura Transformatora
|
||
Architektura transformatora jest podstawą wielu LLMs. Składa się z struktury kodera-dekodera, gdzie koder przetwarza sekwencję wejściową, a dekoder generuje sekwencję wyjściową. Kluczowe składniki architektury transformatora obejmują:
|
||
- **Mechanizm Samouważności**: Ten mechanizm pozwala modelowi ocenić znaczenie różnych słów w sekwencji podczas generowania reprezentacji. Oblicza wyniki uwagi na podstawie relacji między słowami, co umożliwia modelowi skupienie się na odpowiednim kontekście.
|
||
- **Uwaga Wielogłowa**: Ten komponent pozwala modelowi uchwycić wiele relacji między słowami, używając wielu głów uwagi, z których każda koncentruje się na różnych aspektach wejścia.
|
||
- **Kodowanie Pozycyjne**: Ponieważ transformatory nie mają wbudowanego pojęcia kolejności słów, kodowanie pozycyjne jest dodawane do osadzeń wejściowych, aby dostarczyć informacji o pozycji słów w sekwencji.
|
||
|
||
## Modele Dyfuzji
|
||
Modele dyfuzji to klasa modeli generatywnych, które uczą się generować dane, symulując proces dyfuzji. Są szczególnie skuteczne w zadaniach takich jak generowanie obrazów i zyskały popularność w ostatnich latach.
|
||
Modele dyfuzji działają poprzez stopniowe przekształcanie prostej rozkładu szumów w złożony rozkład danych poprzez szereg kroków dyfuzji. Kluczowe składniki modeli dyfuzji obejmują:
|
||
- **Proces Dyfuzji Naprzód**: Ten proces stopniowo dodaje szum do danych, przekształcając je w prosty rozkład szumów. Proces dyfuzji naprzód jest zazwyczaj definiowany przez szereg poziomów szumów, gdzie każdy poziom odpowiada określonej ilości szumu dodanego do danych.
|
||
- **Proces Dyfuzji Wstecz**: Ten proces uczy się odwracać proces dyfuzji naprzód, stopniowo usuwając szum z danych, aby generować próbki z docelowego rozkładu. Proces dyfuzji wstecz jest trenowany przy użyciu funkcji straty, która zachęca model do rekonstrukcji oryginalnych danych z zaszumionych próbek.
|
||
|
||
Ponadto, aby wygenerować obraz z tekstowego podpowiedzi, modele dyfuzji zazwyczaj wykonują następujące kroki:
|
||
1. **Kodowanie Tekstu**: Tekstowa podpowiedź jest kodowana w latentną reprezentację za pomocą kodera tekstu (np. modelu opartego na transformatorze). Ta reprezentacja uchwyca semantyczne znaczenie tekstu.
|
||
2. **Próbkowanie Szumu**: Losowy wektor szumu jest próbkowany z rozkładu Gaussa.
|
||
3. **Kroki Dyfuzji**: Model stosuje szereg kroków dyfuzji, stopniowo przekształcając wektor szumu w obraz, który odpowiada tekstowej podpowiedzi. Każdy krok polega na zastosowaniu wyuczonych transformacji w celu usunięcia szumu z obrazu.
|
||
|
||
{{#include ../banners/hacktricks-training.md}}
|