PyTorch Rekurrentes Neuronales Netz (RNN)

Rekurrente Neuronale Netze (RNN) sind eine Klasse von neuronalen Netzarchitekturen, die speziell für die Verarbeitung von Sequenzdaten entwickelt wurden. Sie können dynamische Informationen aus Zeitreihen oder geordneten Daten erfassen und Sequenzdaten wie Text, Zeitreihen oder Audio verarbeiten.

RNN haben ein breites Anwendungsspektrum in Aufgaben wie Verarbeitung natürlicher Sprache (NLP), Spracherkennung und Zeitreihenvorhersage.

Ein Hauptmerkmal von RNN ist ihre Fähigkeit, einen versteckten Zustand beizubehalten, wodurch das Netzwerk Informationen aus vorherigen Zeitschritten speichern kann, was für die Verarbeitung von Sequenzdaten unerlässlich ist.

Grundstruktur von RNN

In einem traditionellen Feedforward-Neuronalen Netz fließen Daten von der Eingabeschicht zur Ausgabeschicht. In einem RNN hingegen fließen die Daten nicht nur durch die Netzwerkschichten, sondern breiten sich bei jedem Zeitschritt auch auf den aktuellen Zustand der versteckten Schicht aus, wodurch vorherige Informationen an den nächsten Zeitschritt weitergegeben werden.

Versteckter Zustand:RNN verwendet den versteckten Zustand, um Informationen in der Sequenz zu speichern.

Der versteckte Zustand wird aus dem versteckten Zustand des vorherigen Zeitschritts und der aktuellen Eingabe berechnet.

Die Struktur der rekurrenten Einheit ist wie folgt:

  • Eingabe (\(x_t\)): der Eingabevektor zum Zeitschritt \(t\).
  • Verborgener Zustand (\(h_t\)): der Vektor des verborgenen Zustands zum Zeitschritt \(t\), der verwendet wird, um Informationen aus vorherigen Zeitschritten zu speichern.
  • Ausgabe (\(y_t\)): der Ausgabevektor zum Zeitschritt \(t\) (optional, abhängig von der spezifischen Aufgabe).

Formel:

\[ h_t = f(W_{hh}h_{t-1} + W_{xh}x_t + b_h) \]

  • ht: der verborgene Zustand zum aktuellen Zeitschritt.
  • ht-1: der verborgene Zustand zum vorherigen Zeitschritt.
  • Xt: die Eingabe zum aktuellen Zeitschritt.
  • Whh、Wxh: Gewichtsmatrix.
  • b: Bias-Term.
  • f: Aktivierungsfunktion (wie Tanh oder ReLU).

Ausgabe:Die Ausgabe eines RNN hängt nicht nur von der aktuellen Eingabe, sondern auch von den historischen Informationen des verborgenen Zustands ab.

Formel:

\[ y_t = W_{hy}h_t + b_y \]

  • yt: der Ausgabevektor zum Zeitschritt t (optional, je nach spezifischer Aufgabe).
  • Why: die Gewichtsmatrix vom verborgenen Zustand zur Ausgabe.

Wie RNN Sequenzdaten verarbeitet

Die entfaltete Darstellung eines rekurrenten neuronalen Netzes (RNN) bei der Verarbeitung von Sequenzdaten sieht wie folgt aus:

RNN ist ein neuronales Netz zur Verarbeitung von Sequenzdaten. Es verarbeitet jedes Element in der Sequenz durch rekurrente Verbindungen und gibt bei jedem Zeitschritt Informationen weiter. Im Folgenden wird jeder Teil in der Abbildung erläutert:

  • Eingabesequenz (Xt, Xt-1, Xt+1, ...): Die rosa Kreise in der Abbildung stellen Elemente der Eingabesequenz dar. Zum Beispiel ist Xt die Eingabe am aktuellen Zeitschritt, Xt-1 die Eingabe am vorherigen Zeitschritt und so weiter.

  • Verborgener Zustand (ht, ht-1, ht+1, ...): Die grünen Rechtecke stellen den verborgenen Zustand des RNN dar, der Informationen über die Sequenz zu jedem Zeitschritt speichert. ht ist der verborgene Zustand am aktuellen Zeitschritt, ht-1 ist der verborgene Zustand am vorherigen Zeitschritt.

  • Gewichtsmatrizen (U, W, V):

    • U: Die Gewichtsmatrix von der Eingabe zum verborgenen Zustand, die verwendet wird, um die EingabeXtin einen Teil des verborgenen Zustands umzuwandeln.
    • W: Die Gewichtsmatrix vom verborgenen Zustand zum verborgenen Zustand, die verwendet wird, um den verborgenen Zustand des vorherigen Zeitschrittsht-1in einen Teil des verborgenen Zustands des aktuellen Zeitschritts umzuwandeln.
    • V: Die Gewichtsmatrix vom versteckten Zustand zur Ausgabe, die verwendet wird, um den versteckten Zustandhtin die Ausgabe umzuwandeln.Yt。
  • Ausgabesequenz (Yt, Yt-1, Yt+1, ...): Die blauen Kreise repräsentieren die Ausgabe des RNN zu jedem Zeitschritt. Zum Beispiel ist Yt die Ausgabe zum aktuellen Zeitschritt.

  • Rekurrente Verbindung: Ein Merkmal von RNN ist die rekurrente Verbindung des versteckten Zustands, die es dem Netzwerk ermöglicht, Informationen aus vorherigen Zeitschritten zu berücksichtigen, wenn es die Eingabe am aktuellen Zeitschritt verarbeitet.

  • Entfalten: Die Abbildung zeigt den Entfaltungsprozess des RNN über eine Sequenz, was hilft zu verstehen, wie das RNN Sequenzdaten über die Zeit verarbeitet. In tatsächlichen RNN-Implementierungen werden diese Schritte parallel verarbeitet, aber konzeptionell können wir es entfalten, um zu verstehen, wie Informationen fließen.

  • Informationsfluss: Informationen fließen von der Eingabesequenz über die Gewichtsmatrix U zum versteckten Zustand, dann zwischen Zeitschritten über die Gewichtsmatrix W und schließlich vom versteckten Zustand über die Gewichtsmatrix V zur Ausgabesequenz.


RNN-Grundlagen in PyTorch

In PyTorch kann RNN verwendet werden, um komplexe Sequenzmodelle zu erstellen.

PyTorch bietet mehrere RNN-Module, darunter:

  • torch.nn.RNN: Einfache RNN-Zelle.
  • torch.nn.LSTM: Long Short-Term Memory (LSTM)-Zelle, die langfristige Abhängigkeiten erlernen kann.
  • torch.nn.GRU: Gated Recurrent Unit, eine vereinfachte Version von LSTM, aber normalerweise einfacher zu trainieren.

Bei der Verwendung der RNN-Klasse müssen Sie die Eingabedimension, die Dimension der verborgenen Schicht und einige andere Hyperparameter angeben.

PyTorch Implementierung eines einfachen RNN-Beispiels

Das Folgende ist ein einfaches PyTorch-Implementierungsbeispiel, das ein RNN-Modell verwendet, um Sequenzdaten zu verarbeiten und eine Klassifizierung durchzuführen.

1. Notwendige Bibliotheken importieren

Beispiel

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import numpy as np

2. Definiere das RNN-Modell

Beispiel

class SimpleRNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleRNN, self).__init__()
        # Definiere die RNN-Schicht
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        # Definiere die vollständig verbundene Schicht
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        # x: (batch_size, seq_len, input_size)
        out, _ = self.rnn(x)  # out: (batch_size, seq_len, hidden_size)
        # Verwende die Ausgabe des letzten Zeitschritts der Sequenz als Modellausgabe
        out = out[:, -1, :]  # (batch_size, hidden_size)
        out = self.fc(out)  # Vollständig verbundene Schicht
        return out

3. Trainingsdaten erstellen

Um das RNN zu trainieren, erzeugen wir einige zufällige Sequenzdaten. Hierbei ist das Ziel, den letzten Wert jeder Sequenz als Klassifikationsziel zu verwenden.

Beispiel

# Zufällige Sequenzdaten generieren
num_samples = 1000
seq_len = 10
input_size = 5
output_size = 2  # Binäres Klassifikationsproblem annehmen

# Eingabedaten zufällig generieren (batch_size, seq_len, input_size)
X = torch.randn(num_samples, seq_len, input_size)
# Ziel-Labels zufällig generieren (batch_size, output_size)
Y = torch.randint(0, output_size, (num_samples,))

# Data Loader erstellen
dataset = TensorDataset(X, Y)
train_loader = DataLoader(dataset, batch_size=32, shuffle=True)

4. Verlustfunktion und Optimierer definieren

Beispiel

# Modell instanziieren
model = SimpleRNN(input_size=input_size, hidden_size=64, output_size=output_size)

# Verlustfunktion und Optimierer definieren
criterion = nn.CrossEntropyLoss()  # Multi-Klassen-Cross-Entropy-Verlust
optimizer = optim.Adam(model.parameters(), lr=0.001)

5. Das Modell trainieren

Beispiel

num_epochs = 10

for epoch in range(num_epochs):
    model.train()  # Setze das Modell in den Trainingsmodus
    total_loss = 0
    correct = 0
    total = 0

    for inputs, labels in train_loader:
        # Vorwärtsdurchlauf
        outputs = model(inputs)
        loss = criterion(outputs, labels)

        # Backpropagation und Optimierung
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_loss += loss.item()

        # Genauigkeit berechnen
        _, predicted = torch.max(outputs, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

    accuracy = 100 * correct / total
    print(f"Epoche [{epoch+1}/{num_epochs}], Verlust: {total_loss / len(train_loader):.4f}, Genauigkeit: {accuracy:.2f}%")

6. Das Modell testen

Nach dem Training können wir die Leistung des Modells auf dem Testset bewerten.

Beispiel

# Das Modell testen
model.eval()  # Modell in den Evaluierungsmodus versetzen
with torch.no_grad():
    total = 0
    correct = 0
    for inputs, labels in train_loader:
        outputs = model(inputs)
        _, predicted = torch.max(outputs, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

    accuracy = 100 * correct / total
    print(f"Testgenauigkeit: {accuracy:.2f}%")

7. Der vollständige Code lautet wie folgt:

Beispiel

import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np

# Datensatz: Vorhersage von Zeichenfolgen (Hello -> Elloh)
char_set = list("hello")
char_to_idx = {c: i for i, c in enumerate(char_set)}
idx_to_char = {i: c for i, c in enumerate(char_set)}

# Datenvorbereitung
input_str = "hello"
target_str = "elloh"
input_data = [char_to_idx[c] for c in input_str]
target_data = [char_to_idx[c] for c in target_str]

# In One-Hot-Encoding umwandeln
input_one_hot = np.eye(len(char_set))[input_data]

# In PyTorch-Tensor konvertieren
inputs = torch.tensor(input_one_hot, dtype=torch.float32)
targets = torch.tensor(target_data, dtype=torch.long)

# Modell-Hyperparameter
input_size = len(char_set)
hidden_size = 8
output_size = len(char_set)
num_epochs = 200
learning_rate = 0.1

# RNN-Modell definieren
class RNNModel(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(RNNModel, self).__init__()
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x, hidden):
        out, hidden = self.rnn(x, hidden)
        out = self.fc(out)  # Vollständig verbundene Schicht anwenden
        return out, hidden

model = RNNModel(input_size, hidden_size, output_size)

# Verlustfunktion und Optimierer definieren
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# Das RNN trainieren
losses = []
hidden = None  # Anfänglicher Hidden-Zustand ist None
for epoch in range(num_epochs):
    optimizer.zero_grad()

    # Vorwärtsdurchlauf
    outputs, hidden = model(inputs.unsqueeze(0), hidden)
    hidden = hidden.detach()  # Gradientenexplosion verhindern

    # Verlust berechnen
    loss = criterion(outputs.view(-1, output_size), targets)
    loss.backward()
    optimizer.step()
    losses.append(loss.item())

    if (epoch + 1) % 20 == 0:
        print(f"Epoche [{epoch + 1}/{num_epochs}], Verlust: {loss.item():.4f}")

# Das RNN testen
with torch.no_grad():
    test_hidden = None
    test_output, _ = model(inputs.unsqueeze(0), test_hidden)
    predicted = torch.argmax(test_output, dim=2).squeeze().numpy()

    print("Eingabesequenz: ", ''.join([idx_to_char[i] for i in input_data]))
    print("Vorhergesagte Sequenz: ", ''.join([idx_to_char[i] for i in predicted]))

Code-Erklärung:

  1. Datenvorbereitung:

    • Verwenden Sie die Zeichensequenzhello, und konvertieren Sie sie in One-Hot-Encoding.
    • Die Zielsequenz istelloh, d. h., ein Zeichen nach rechts rotieren.
  2. Modellkonstruktion:

    • Verwenden Sietorch.nn.RNNErstellen Sie ein rekurrentes neuronales Netz.
    • Füge eine vollständig verbundene Schicht hinzutorch.nn.LinearWird verwendet, um den verborgenen Zustand auf die Ausgabe abzubilden.
  3. Trainingsteil:

    • Jede Epoche berechnet den Verlust und führt eine Backpropagation durch.
    • Der verborgene Zustand wird durchgeleitethidden.detach()um eine Gradientenexplosion zu verhindern.
  4. Testteil:

    • Das Modell gibt die vorhergesagten Zeichenergebnisse aus.
  5. Visualisierung:

    • Verwende Matplotlib, um den Trend des Trainingsverlusts darzustellen.

Angenommen, Ihr Modell ist gut trainiert, könnte die Ausgabe wie folgt aussehen:

Epoch [20/200], Loss: 0.0013
Epoch [40/200], Loss: 0.0003
Epoch [60/200], Loss: 0.0002
Epoch [80/200], Loss: 0.0001
Epoch [100/200], Loss: 0.0001
Epoch [120/200], Loss: 0.0001
Epoch [140/200], Loss: 0.0001
Epoch [160/200], Loss: 0.0001
Epoch [180/200], Loss: 0.0001
Epoch [200/200], Loss: 0.0001
Input sequence:  hello

Anhand der Ergebnisse zeigt das Bild einen allmählichen Rückgang des Verlusts, was darauf hindeutet, dass das Modelltraining effektiv ist.

8. Visualisierungscode:

Beispiel

import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np

# Datensatz: Vorhersage von Zeichenfolgen (Hello -> Elloh)
char_set = list("hello")
char_to_idx = {c: i for i, c in enumerate(char_set)}
idx_to_char = {i: c for i, c in enumerate(char_set)}

# Datenvorbereitung
input_str = "hello"
target_str = "elloh"
input_data = [char_to_idx[c] for c in input_str]
target_data = [char_to_idx[c] for c in target_str]

# In One-Hot-Encoding umwandeln
input_one_hot = np.eye(len(char_set))[input_data]

# In PyTorch-Tensor konvertieren
inputs = torch.tensor(input_one_hot, dtype=torch.float32)
targets = torch.tensor(target_data, dtype=torch.long)

# Modell-Hyperparameter
input_size = len(char_set)
hidden_size = 8
output_size = len(char_set)
num_epochs = 200
learning_rate = 0.1

# RNN-Modell definieren
class RNNModel(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(RNNModel, self).__init__()
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x, hidden):
        out, hidden = self.rnn(x, hidden)
        out = self.fc(out)  # Vollständig verbundene Schicht anwenden
        return out, hidden

model = RNNModel(input_size, hidden_size, output_size)

# Verlustfunktion und Optimierer definieren
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# RNN trainieren
losses = []
hidden = None  # Anfänglicher Hidden-Zustand ist None
for epoch in range(num_epochs):
    optimizer.zero_grad()

    # Vorwärtspropagierung
    outputs, hidden = model(inputs.unsqueeze(0), hidden)
    hidden = hidden.detach()  # Gradientenexplosion verhindern

    # Verlust berechnen
    loss = criterion(outputs.view(-1, output_size), targets)
    loss.backward()
    optimizer.step()
    losses.append(loss.item())

    if (epoch + 1) % 20 == 0:
        print(f"Epoche [{epoch + 1}/{num_epochs}], Verlust: {loss.item():.4f}")

# RNN testen
with torch.no_grad():
    test_hidden = None
    test_output, _ = model(inputs.unsqueeze(0), test_hidden)
    predicted = torch.argmax(test_output, dim=2).squeeze().numpy()

    print("Eingabesequenz: ", ''.join([idx_to_char[i] for i in input_data]))
    print("Vorhergesagte Sequenz: ", ''.join([idx_to_char[i] for i in predicted]))

# Verlust visualisieren
plt.plot(losses, label="Trainingsverlust")
plt.xlabel("Epoche")
plt.ylabel("Verlust")
plt.title("RNN Trainingsverlust über Epochen")
plt.legend()
plt.show()

Nach der Ausführung sieht das angezeigte Bild wie folgt aus:

Weitere Erweiterungen