Vollständige Implementierung von Grund auf – Recurrent Neural Network RNN

Eine Einleitung

Verwenden Sie Pytorch, um ein wiederkehrendes neuronales Netzwerk RNN ​​aufzubauen. Recurrent Neural Network (RNN) ist eine Art neuronaler Netzwerkarchitektur, die zur Verarbeitung von Sequenzdaten verwendet wird . Im Gegensatz zu herkömmlichen neuronalen Netzen verfügen RNNs über eine interne Schleifenstruktur , die Zustandsinformationen beibehält, während Sequenzdaten verarbeitet werden. Dies macht RNN in vielen Bereichen wie der Verarbeitung natürlicher Sprache, der Vorhersage von Zeitreihen, der Spracherkennung usw. sehr nützlich.

Referenzlink: Rekurrentes neuronales Netzwerk

1.1 Leitfadenpaket

# 导包
%matplotlib inline

import math
import torch
from torch import nn
from torch.nn import functional as F
import dltools

1.2 Daten laden

1.2.1 Module laden

# Defined in file: ./chapter_recurrent-neural-networks/language-models-and-dataset.md
def load_data_time_machine(batch_size, num_steps, use_random_iter=False,
                           max_tokens=10000):
    """Return the iterator and the vocabulary of the time machine dataset."""
    data_iter = SeqDataLoader(batch_size, num_steps, use_random_iter,
                              max_tokens)
    return data_iter, data_iter.vocab

# 加载 time, machine 数据
batch_size, num_steps = 32, 35
train_iter, vocab = dltools.load_data_time_machine(batch_size=batch_size, num_steps=num_steps)

Load_data_time_machine wird hauptsächlich zum Laden von Zeitreihendaten verwendet.

Diese Funktion gibt zwei Werte zurück:

  1. data_iter: Dies ist ein Sequenzdateniterator, der zum Generieren von Stapeln von Trainingsdaten verwendet wird. Dieser Iterator wird normalerweise verwendet, um wiederkehrende neuronale Netze zu trainieren und die Daten in mehrere Stapel aufzuteilen. Jeder Stapel enthält mehrere Sequenzen. Jede Sequenz hat eine feste Anzahl von Zeitschritten. Es handelt sich um Zeitreihendaten. Jeder Stapel besteht aus zwei Listen. Eine ist X, und der andere ist der Zielwert y.
  2. data_iter_vocab: Dies ist das Vokabular des Datensatzes, das alle möglichen Token (z. B. Wörter oder Zeichen) im Datensatz und ihre entsprechenden Indizes enthält. Das Vokabular ist wichtig, da es Textdaten in eine numerische Darstellung umwandelt, die das Modell verstehen kann. Normalerweise sollte es ein Wörterbuch mit 28 Zeichen und numerischen Werten sein.

1.2.2 Datenlademethode, ob Zufallsdaten verwendet werden sollen

# Defined in file
class SeqDataLoader:
    """An iterator to load sequence data."""
    def __init__(self, batch_size, num_steps, use_random_iter, max_tokens):
        if use_random_iter:
            self.data_iter_fn = dltools.seq_data_iter_random
        else:
            self.data_iter_fn = dltools.seq_data_iter_sequential
        self.corpus, self.vocab = dltools.load_corpus_time_machine(max_tokens)
        self.batch_size, self.num_steps = batch_size, num_steps

    def __iter__(self):
        return self.data_iter_fn(self.corpus, self.batch_size, self.num_steps)

def seq_data_iter_random(corpus, batch_size, num_steps):
    corpus = corpus[random.randint(0, num_steps - 1):]
    num_subseqs = (len(corpus) - 1) // num_steps
    initial_indices = list(range(0, num_subseqs * num_steps, num_steps))
    random.shuffle(initial_indices)

    def data(pos):
        return corpus[pos:pos + num_steps]

    num_batches = num_subseqs // batch_size
    for i in range(0, batch_size * num_batches, batch_size):
        initial_indices_per_batch = initial_indices[i:i + batch_size]
        X = [data(j) for j in initial_indices_per_batch]
        Y = [data(j + 1) for j in initial_indices_per_batch]
        yield dltools.tensor(X), dltools.tensor(Y)


def seq_data_iter_sequential(corpus, batch_size, num_steps):
    """Generate a minibatch of subsequences using sequential partitioning."""
    # Start with a random offset to partition a sequence
    offset = random.randint(0, num_steps)
    num_tokens = ((len(corpus) - offset - 1) // batch_size) * batch_size
    Xs = dltools.tensor(corpus[offset:offset + num_tokens])
    Ys = dltools.tensor(corpus[offset + 1:offset + 1 + num_tokens])
    Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)
    num_batches = Xs.shape[1] // num_steps
    for i in range(0, num_steps * num_batches, num_steps):
        X = Xs[:, i:i + num_steps]
        Y = Ys[:, i:i + num_steps]
        yield X, Y

Dieser Code definiert eine Python-Klasse namens SeDataLoader, die ein Iterator zum Laden von Sequenzdaten ist. Im Folgenden werden die wichtigsten Eigenschaften und Funktionen dieser Klasse erläutert:

  1. Batch_size: Gibt die Anzahl der Sequenzen in jedem Batch an.
  2. num_steps: Gibt die Anzahl oder Länge jeder Sequenz an.
  3. use_random_iter: Ein boolescher Wert, der angibt, ob ein zufälliger Iterator verwendet werden soll. Bei der Einstellung Truewerden die Daten zufällig als Trainingsbeispiele ausgewählt; bei der Einstellung Falsewerden die Daten in einer festen Reihenfolge als Trainingsbeispiele ausgewählt.
  4. max_tokens: wird verwendet, um die maximale Anzahl von Token im Datensatz zu begrenzen. Wird normalerweise verwendet, um die Größe des Wortschatzes zu begrenzen.

seq_data_iter_sequentialDie Funktion wird verwendet, um einen Minibatch von Sequenzdaten zu generieren. Diese Funktion wird verwendet, um den Datensatz sequentiell aufzuteilen.

Im Folgenden sind die Hauptschritte und Funktionserklärungen der Funktion aufgeführt:

  • corpus: Eingabesequenzdaten, normalerweise eine Liste oder ein Array mit Ganzzahlmarkierungen.
  • batch_size: Anzahl der Sequenzen in jedem Mini-Batch.
  • num_steps: Die Anzahl oder Länge jeder Sequenz.

Wenn die Funktion startet, wählt sie zufällig einen Offset aus offset, der zum Teilen der Reihe vom Datensatz verwendet wird. Der Zweck dieses Offsets besteht darin, die Zufälligkeit der Daten zu erhöhen, um das Modell besser zu trainieren.

1.2.3 Tatsächliche Belastung der Module

def load_corpus_time_machine(max_tokens=-1):
    """Return token indices and the vocabulary of the time machine dataset."""
    lines = read_time_machine()
    tokens = tokenize(lines, 'char')
    vocab = Vocab(tokens)
    # Since each text line in the time machine dataset is not necessarily a
    # sentence or a paragraph, flatten all the text lines into a single list
    corpus = [vocab[token] for line in tokens for token in line]
    if max_tokens > 0:
        corpus = corpus[:max_tokens]
    return corpus, vocab

def read_time_machine():
    with open('./article.txt', 'r') as f:
        lines = f.readlines()
    return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]

1.2.4 Anzeigen des Ladeabschlusseffekts

for x, y in train_iter:
    print('x: ', x)
    print('y: ', y)
    break

# 输入数据,打算输入 one_hot编码
vocab.token_to_idx

# 输入数据,我们是打算输入 one_hot 编码的数据
# pytorch 提供了快速进行one_hot 编码的工具
from torch.nn import functional as F
F.one_hot(torch.tensor([0, 2]), num_classes=len(vocab))

2. Initialisierungsmodell

2.1 Initialisierungsparameter

# 初始化模型参数
def get_params(vocab_size, num_hiddens, device):
    num_inputs = num_outputs = vocab_size
    
    # 内部函数
    def normal(shape):
        return torch.randn(size=shape, device=device) * 0.01
    
    # 隐藏层的参数
    W_xh = normal((num_inputs, num_hiddens))
    W_hh = normal((num_hiddens, num_hiddens))
    b_h = torch.zeros(num_hiddens, device=device)
    
    # 输出层参数
    W_hq = normal((num_hiddens, num_outputs))
    b_q = torch.zeros(num_outputs, device=device)
    
    # nn.parameter(w_xh) 默认可以求导
    # 把参数都设置 requires_gard = True
    params = [W_xh, W_hh, b_h, W_hq, b_q]
    for param in params:
        param.requires_grad_(True)
    return params

Eine Funktion, die die Parameter eines RNN-Modells (Recurrent Neural Network) initialisiert get_params. Diese Funktion gibt eine Liste mit den Modellparametern zurück, einschließlich der Gewichtungen und Bias der verborgenen Schicht sowie der Gewichte und Bias der Ausgabeschicht.

Hier sind die Hauptteile der Funktion und eine Erklärung ihrer Funktionsweise:

  • vocab_size: Die Größe des Vokabulars, normalerweise entsprechend der Anzahl der Token in den Ein- und Ausgängen des Modells.
  • num_hiddens: Anzahl der Neuronen in der verborgenen Schicht, dies ist ein Hyperparameter.
  • device: Gibt das Computergerät an, auf dem sich die Modellparameter befinden, normalerweise eine GPU oder CPU.

Die Funktion berechnet zunächst num_inputsund num_outputs, ihre Werte sind gleich vocab_size, da die Eingabe- und Ausgabeanzahl der Marker dieses RNN-Modells gleich sind.

Als nächstes definiert die Funktion eine interne Funktion, normal(shape)die verwendet wird, um einen Tensor der angegebenen Form zu generieren, dessen Werte zufällig aus der Standardnormalverteilung (Mittelwert 0, Standardabweichung 1) ausgewählt und dann mit 0,01 multipliziert werden . Dies ist eine häufig verwendete Methode zum Initialisieren von Gewichten.

Die Funktion erstellte dann die folgenden Parameter:

  1. W_xh: Die in die verborgene Ebene eingegebene Gewichtsmatrix mit Form (num_inputs, num_hiddens).
  2. W_hh: Gewichtsmatrix von verborgener Schicht zu verborgener Schicht, Form ist (num_hiddens, num_hiddens).
  3. b_h: Versatz der verborgenen Ebene , Form ist (num_hiddens,).
  4. W_hq: Die Gewichtsmatrix von der verborgenen Ebene zur Ausgabeebene mit der Form (num_hiddens, num_outputs).
  5. b_q: Die Ausrichtung der Ausgabeebene mit der Form (num_outputs,).

Die Formen und Initialisierungswerte dieser Parameter werden basierend auf vocab_sizeund num_hiddensberechnet . Die Gewichtsmatrix wird zufällig initialisiert und die Verzerrungen werden auf Null initialisiert.

Schließlich fügt die Funktion diese Parameter in eine Liste ein und setzt paramsihr Attribut auf , was anzeigt, dass diese Parameter während des Modelltrainings Gradienten für Backpropagation und Parameteraktualisierungen berechnen müssen .requires_gradTrue

Zusammenfassend get_paramsinitialisiert die Funktion die Parameter eines wiederkehrenden neuronalen Netzwerkmodells basierend auf den angegebenen Hyperparametern und der Vokabulargröße und gibt eine Liste dieser Parameter zurück. Diese Parameter werden während des Trainingsprozesses des Modells kontinuierlich aktualisiert, um sie an die Trainingsdaten anzupassen.

2.2 Verborgenen Zustand initialisieren

# 初始化时返回隐藏状态
def init_rnn_state(batch_size, num_hiddens, device):
    # 返回元组
    return (torch.zeros((batch_size, num_hiddens), device=device),)

Die Hauptfunktion besteht darin, den verborgenen Zustand des Recurrent Neural Network (RNN) zu initialisieren und zurückzugeben. Hier ist eine detaillierte Erklärung dieser Funktion:

  • batch_size: Die Größe der Chargendaten, d. h. die Anzahl der Proben in jeder Minicharge.
  • num_hiddens: Die Anzahl der Neuronen in der verborgenen Schicht, dh die Anzahl der verborgenen Einheiten von RNN.
  • device: Gibt das Computergerät an, auf dem sich der initialisierte Tensor befindet, normalerweise GPU oder CPU.

Die Funktion gibt ein Tupel zurück, das den verborgenen Zustand enthält, wobei das einzige Element im Tupel ein Tensor ist, der den initialisierten verborgenen Zustand darstellt. In diesem Tupel hat der Tensor die Form(batch_size, num_hiddens) , er besteht ausschließlich aus Nullen . Dieser Tensor wird verwendet, um den anfänglichen verborgenen Zustand des RNN-Modells darzustellen.

Beim Training eines RNN-Modells ist es normalerweise erforderlich, den verborgenen Zustand zu initialisieren und ihn dann bei jedem Zeitschritt zu aktualisieren. Mit dieser Funktion wird der anfängliche verborgene Zustand generiert, um ihn an den ersten Zeitschritt des RNN-Modells zu übergeben. In nachfolgenden Zeitschritten des Modells wird der verborgene Zustand des vorherigen Zeitschritts verwendet, um den verborgenen Zustand des aktuellen Zeitschritts zu aktualisieren.

2.3 RNN-Hauptstruktur

# rnn主体结构
def rnn(inputs, state, params):
    # inputs的形状: 时间步数量,批次大小,词表大小
    W_xh, W_hh, b_h, W_hq, b_q = params
    H, = state
    outputs = []
    # X的shape:【批次大小, 词表大小】
    for X in inputs:
        # 一般在循环神经网络中,激活函数使用tanh比较多
        H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)
        Y = torch.mm(H, W_hq) + b_q
        outputs.append(Y)
    return torch.cat(outputs, dim=0), (H,)

Die Hauptstruktur eines RNN (Recurrent Neural Network) wird definiert, einschließlich des Prozesses der Vorwärtsausbreitung . Hier ist eine detaillierte Erklärung dieser Funktion:

  • inputs: Eingabedaten mit der Form (时间步数量,批次大小,词表大小), wobei:

    • 时间步数量Gibt an, wie viele Zeitschritte an Daten das RNN-Modell verarbeiten wird.
    • 批次大小Gibt an, wie viele Proben es zu jedem Zeitschritt gibt.
    • 词表大小Stellt die Dimensionalität der Eingabemerkmale in jedem Zeitschritt dar und wird normalerweise zur Darstellung von Worteinbettungen oder Merkmalsvektoren verwendet.
  • state: Der anfängliche verborgene Zustand ist ein Tupel, dessen einziges Element ein Tensor ist H, der die Form des verborgenen Zustands darstellt (批次大小,隐藏单元数量).

  • params: Enthält eine Liste von Modellparametern, einschließlich Gewichtungen und Bias-Parametern.

Die Hauptlogik der Funktion besteht darin, jeden Zeitschritt in den Eingabedaten zu durchlaufen und für jeden Zeitschritt die folgenden Operationen auszuführen:

  1. Unter Verwendung der Eingabe Xsowie des aktuellen verborgenen Zustands Hwird ein neuer verborgener Zustand durch Matrixmultiplikation und eine Aktivierungsfunktion (tanh) berechnet H.
  2. Unter Verwendung des neuen verborgenen Zustands Hwird die Ausgabe durch Matrixmultiplikation und Bias-Terme berechnet Y.

Dieser Vorgang wird bei jedem Zeitschritt wiederholt, wobei jedes Mal der verborgene Zustand des vorherigen Zeitschritts als Eingabe für den aktuellen Zeitschritt verwendet wird, um Abhängigkeiten in den Sequenzdaten zu modellieren. Schließlich wird die Ausgabe aller Zeitschritte zu einem Tensor verkettet und als Rückgabewert der Funktion verwendet. Außerdem wird der verborgene Zustand des letzten Zeitschritts zurückgegeben.

Die Ausgabe dieser Funktion besteht aus zwei Teilen:

  • torch.cat(outputs, dim=0): Verketten Sie die Ausgaben aller Zeitschritte zu einem Tensor mit der Dimension (时间步数量 * 批次大小, 词表大小). Dieser Tensor enthält die Ausgabe für jeden Zeitschritt.
  • (H,): Der verborgene Zustand des letzten Zeitschritts, zurückgegeben als Tupel, der die Form von darstellt (批次大小,隐藏单元数量).

Zusammenfassend implementiert diese Funktion den Vorwärtsausbreitungsprozess des RNN-Modells, wandelt die Eingabedatensequenz in eine Ausgabesequenz um und behält den verborgenen Zustand für die Verwendung in nachfolgenden Zeitschritten bei.

2.3.1torch.cat

torch.catIst die Funktion, die für die Tensorverkettung (Verkettung) in PyTorch verwendet wird. Es ermöglicht Ihnen, mehrere Tensoren in einer bestimmten Dimension zu verketten, um einen neuen Tensor zu erstellen.

2.3.2torch.mm

torch.mmIst die Matrixmultiplikationsfunktion in PyTorch. Es wird verwendet, um die Matrixmultiplikation zweier 2D-Tensoren (Matrizen) zu berechnen. Konkret torch.mmwird das innere Produkt zweier Matrizen berechnet.

Drei Pakete in Kategorien

# 包装成类
class RNNModelScratch:
    def __init__(self, vocab_size, num_hiddens, device, get_params, init_state, forward_fn):
        self.vocab_size, self.num_hiddens = vocab_size, num_hiddens
        self.params = get_params(vocab_size, num_hiddens, device)
        self.init_state, self.forward_fn = init_state, forward_fn
        
    def __call__(self, X, state):
        X = F.one_hot(X.T, self.vocab_size).type(torch.float32)
        return self.forward_fn(X, state, self.params)
    
    def begin_state(self, batch_size, device):
        return self.init_state(batch_size, self.num_hiddens, device)

Es wird eine grundlegende Modellklasse für wiederkehrende neuronale Netze (RNN) definiert RNNModelScratch, die zum Aufbau eines einfachen wiederkehrenden neuronalen Netzmodells auf Basis von PyTorch verwendet wird. Hier ist eine zeilenweise Erklärung des Codes:

  1. class RNNModelScratch:: Dies ist die Definition einer Python-Klasse, die zum Erstellen einer Instanz eines RNN-Modells verwendet wird.

  2. def __init__(self, vocab_size, num_hiddens, device, get_params, init_state, forward_fn):: Dies ist der Konstruktor der Klasse __init__, der zum Initialisieren der Parameter und der Konfiguration des RNN-Modells verwendet wird . Es akzeptiert die folgenden Parameter:

    • vocab_size: Die Größe des Vokabulars , die zum Definieren der Dimensionen der Eingabedaten (Vokabulargröße) verwendet wird.
    • num_hiddens: Die Größe der verborgenen Schicht , die die Dimension des verborgenen Zustands im RNN-Modell definiert.
    • device: Geben Sie an, auf welchem ​​Computergerät das Modell ausgeführt wird , z. B. CPU oder GPU.
    • get_params: Eine Funktion zum Initialisieren der Parameter des Modells .
    • init_state: Eine Funktion, die zum Initialisieren des verborgenen Zustands von RNN verwendet wird .
    • forward_fn: Eine Funktion zur Definition des Vorwärtsausbreitungsprozesses von RNN.
  3. self.vocab_size, self.num_hiddens = vocab_size, num_hiddens: Diese Zeile speichert die an den Konstruktor übergebenen Parameter in Mitgliedsvariablen der Klasse zur Verwendung in der gesamten Klasse .

  4. self.params = get_params(vocab_size, num_hiddens, device): Diese Zeile ruft die übergebene get_paramsFunktion auf, um die Parameter des Modells zu initialisieren und sie in zu speichern self.params.

  5. self.init_state, self.forward_fn = init_state, forward_fn: Diese Zeile speichert die übergebenen Funktionen init_stateund forward_fnin Mitgliedsvariablen der Klasse zur Verwendung im Modell.

  6. def __call__(self, X, state):: Dies ist eine spezielle Methode einer Klasse __call__, die es ermöglicht, Instanzen der Klasse wie Funktionen aufzurufen . Mit dieser Methode wird definiert, wie eine Vorwärtspropagierung durchgeführt wird.

    • X: Eingabedaten, normalerweise ein Stapel von Sequenzdaten.
    • state: Der verborgene Zustand von RNN.
  7. X = F.one_hot(X.T, self.vocab_size).type(torch.float32): Diese Zeile Xwandelt die Eingabedaten in eine One-Hot-Codierung für die Eingabe in das Modell um. F.one_hotist eine Funktion in PyTorch, die eine Folge von Ganzzahlen in einen One-Hot-codierten Tensor umwandelt.

  8. return self.forward_fn(X, state, self.params): Diese Zeile ruft die übergebene forward_fnFunktion auf , um eine Vorwärtspropagierung des Modells durchzuführen und die Ausgabe zu berechnen.

  9. def begin_state(self, batch_size, device):: Diese Methode wird verwendet, um den verborgenen Zustand von RNN zur Verwendung während des Trainings zu initialisieren.

    • batch_size: Chargengröße, definiert die Anzahl der Proben in jeder Charge.
    • device:Gibt das Computergerät an.

Der Zweck dieser Klasse besteht darin, ein einfaches RNN-Modell zu erstellen, das die Initialisierung von Modellparametern, die Definition der Vorwärtsausbreitungsmethode und die Initialisierung verborgener Zustände umfasst. Mit dieser Klasse kann ein grundlegendes rekurrentes neuronales Netzwerkmodell erstellt und trainiert werden.

3.1 Gerätedefinition

device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
device = dltools.try_gpu()

def try_gpu(i=0):
    """Return gpu(i) if exists, otherwise return cpu()."""
    if torch.cuda.device_count() >= i + 1:
        return torch.device(f'cuda:{i}')
    return torch.device('cpu')

4. Rufen Sie das definierte RNN auf

# 使用该类
num_hiddens = 512
net = RNNModelScratch(len(vocab), num_hiddens, dltools.try_gpu(), get_params, init_rnn_state, rnn)
state = net.begin_state(X.shape[0], dltools.try_gpu())
Y, new_state = net(X.to(dltools.try_gpu()), state)

4.1 Sehen Sie sich den Definitionseffekt an

Y.shape

new_state

vocab.__getitem__('a')  # 输出为 4 

4.2 Verwendung von Modellen zur Vorhersage

# 预测
def predict(prefix, num_preds, net, vocab, device):
    state = net.begin_state(batch_size=1, device=device)
    outputs = [vocab[prefix[0]]]
    get_input = lambda: torch.tensor([outputs[-1]], device = device).reshape((1, 1))
    # 预热
    for y in prefix[1:]:
        _, state = net(get_input(), state)
        outputs.append(vocab[y])
        
    # 真正的预测
    for _ in range(num_preds):
        y, state = net(get_input(), state)
        outputs.append(int(y.argmax(dim=1).reshape(1)))
    return ''.join([vocab.idx_to_token[i] for i in outputs])

Definiert eine Funktion zum Generieren von Textsequenzvorhersagen predict. Ihre Funktion besteht darin, vorhergesagten Text mithilfe eines trainierten rekurrenten neuronalen Netzwerkmodells zu generieren. Hier ist eine zeilenweise Erklärung des Codes:

  1. def predict(prefix, num_preds, net, vocab, device):: Dies ist eine Funktionsdefinition, die zum Generieren von Vorhersagen für Textsequenzen verwendet wird.

    • prefix: Eine Liste von Zeichenfolgen, die die vorhergesagten Startpräfixe darstellen.
    • num_preds: Geben Sie die Länge des generierten Vorhersagetextes an .
    • net: Das trainierte wiederkehrende neuronale Netzwerkmodell .
    • vocab: Vokabular zum Konvertieren der Modellausgabe in Text.
    • device: Geben Sie das Computergerät an, z. B. CPU oder GPU.
  2. state = net.begin_state(batch_size=1, device=device): Diese Zeile initialisiert den verborgenen Zustand des RNN-Modells . net.begin_stateDie Methode gibt ein Tupel zurück, das den verborgenen Zustand des RNN-Modells enthält. Hier batch_sizeauf 1 setzen, was bedeutet, dass jeweils ein Zeichen generiert wird.

  3. outputs = [vocab[prefix[0]]]: Initialisierungsliste outputszum Speichern vorhergesagter Zeichen.

  4. get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1)): Diese Zeile definiert eine Lambda-Funktion, get_inputum die Eingabe des Modells zu erhalten . Es outputswandelt das letzte Zeichen der Liste in einen PyTorch-Tensor um und passt die Form an die Eingabeanforderungen des Modells an.

  5. Aufwärmphase: In dieser Phase akzeptiert das Modell das Präfix prefixals Eingabe und wird vom Modell aufgewärmt. Der Zweck des Vorwärmens besteht darin, den verborgenen Zustand des Modells für den nachfolgenden Generierungsprozess in den präfixierten Zustand zu initialisieren. Der folgende Code durchläuft die Präfixzeichen und führt das Modell bei jedem Zeitschritt aus, wobei der verborgene Status und die Ausgabe aktualisiert werden.

  6. Die eigentliche Vorhersagephase: In dieser Phase beginnt das Modell mit der Generierung von Vorhersagetext . Das Modell akzeptiert die Ausgabe des vorherigen Zeitschritts als Eingabe für den aktuellen Zeitschritt und führt das Modell aus, um neue Zeichen zu generieren. outputsDer folgende Code führt eine Schleife aus, um vorhergesagte Zeichen zu generieren und sie der Liste hinzuzufügen :

  7. return ''.join([vocab.idx_to_token[i] for i in outputs]): Konvertieren Sie abschließend die vom Modell generierte Liste der Zeichen wieder in eine Zeichenfolge und geben Sie den resultierenden vorhergesagten Text zurück.

Insgesamt verwendet diese Funktion ein trainiertes rekurrentes neuronales Netzwerkmodell, um Vorhersagen für Textsequenzen mit einem Präfix zu generieren. Während des Vorhersageprozesses generiert das Modell das nächste Zeichen basierend auf der Ausgabe des vorherigen Zeitschritts und generiert nach und nach die gesamte Textsequenz.

4.3 Verlaufsausschnitt

# 梯度裁剪
def grad_clipping(net, theta):
    if isinstance(net, nn.Module):
        params = [p for p in net.parameters() if p.requires_grad]
    else:
        params = net.params
    
    norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
    if norm > theta:
        for param in params:
            param.grad[:] *= theta / norm

Funktion zum Beschneiden des Farbverlaufs implementiert, um das Problem der Farbverlaufsexplosion zu verhindern. Hier ist eine zeilenweise Erklärung des Codes:

  1. def grad_clipping(net, theta):: Dies ist eine Funktionsdefinition, die zum Beschneiden von Farbverläufen verwendet wird .

    • net: Kann ein PyTorch-Modell ( nn.Moduleeine Unterklasse der Klasse) oder ein benutzerdefiniertes Modellobjekt sein.
    • theta: Der Schwellenwert für die Beschneidung. Wenn die L2-Norm des Gradienten größer als der Schwellenwert ist, wird die Beschneidung durchgeführt.
  2. if isinstance(net, nn.Module):: Diese Zeile prüft , ob es sich um eine Unterklasse der PyTorch- Klasse handelt und wird verwendet, um den Typ des übergebenen Modells zu bestimmen. netnn.Module

  3. params = [p for p in net.parameters() if p.requires_grad]: Wenn es sich um eine Unterklasse der Klasse nethandelt , ruft diese Zeile alle Parameter im Modell ab, die Verlaufsaktualisierungen erfordern . Gibt alle Parameter des Modells zurück und gibt an, ob die Parameter Farbverläufe erfordern.nn.Modulenet.parameters()p.requires_grad

    Wenn netes sich nicht nn.Moduleum eine Unterklasse der Klasse handelt, versucht diese Zeile, die Parameterliste im benutzerdefinierten Modellobjekt abzurufen, vorausgesetzt, das benutzerdefinierte Modellobjekt verfügt über eine Eigenschaft mit dem Namen params.

  4. norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params)): Berechnen Sie die L2-Norm aller Parametergradienten . Diese Norm stellt die Gesamtgröße aller Parametergradienten dar. Konkret werden hier zwei Summierungen verwendet. pBerechnen Sie zunächst jeden Parameter p.grad ** 2und summieren Sie ihn dann torch.summithilfe von . Summieren Sie als Nächstes sumdie L2-Normen aller Parameter mit .

  5. if norm > theta:: Bestimmen Sie, ob die L2-Norm des Gradienten größer als der angegebene Schwellenwert ist theta. Wenn ja, führen Sie den folgenden Gradientenbeschneidungsvorgang aus.

  6. for param in params:: Durchlaufen Sie die Liste der Parameter, die Verlaufsaktualisierungen erfordern.

  7. param.grad[:] *= theta / norm: Führt einen Clipping-Vorgang für den Farbverlauf jedes Parameters durch . Insbesondere wird der Gradient des Parameters so skaliert, dass er der L2-Norm entspricht und den Schwellenwert nicht überschreitet theta. Hier verwenden wir param.grad[:], um den Gradienten des Parameters direkt zu ändern, indem wir ihn mit dem Skalierungsfaktor multiplizieren theta / norm.

Insgesamt dient dieser Code zum Beschneiden von Farbverläufen und kann auf jedes PyTorch-Modell oder benutzerdefinierte Modell angewendet werden. Es berechnet die L2-Norm der Gradienten aller Parameter und vergleicht sie mit einem festgelegten Schwellenwert. Bei Überschreiten des Schwellenwerts wird der Gradient proportional skaliert, um sicherzustellen, dass der Gradient nicht explodiert. Dies trägt dazu bei, die Trainingsstabilität des Modells zu verbessern.

 Fünf Training

# 训练
def train_epoch(net, train_iter, loss, updater, device, use_random_iter):
    state, timer = None, dltools.Timer()
    metric = dltools.Accumulator(2)
    for X, Y in train_iter:
        if state is None or use_random_iter:
            state = net.begin_state(batch_size=X.shape[0], device=device)
        else:
            if isinstance(net, nn.Module) and not isinstance(state, tuple):
                state.detach_()
            else:
                for s in state:
                    s.detach_()
        y = Y.T.reshape(-1)
        X, y = X.to(device), y.to(device)
        y_hat, state = net(X, state)
        l = loss(y_hat, y.long()).mean()
        if isinstance(updater, torch.optim.Optimizer):
            updater.zero_grad()
            l.backward()
            grad_clippling(net, 1)
            updater.step()
        else:
            l.backward()
            grad_clipping(net, 1)
            updater(batch_size=1)
        metric.add(l * y.numel(), y.numel())  # !!!! l 不是1
    # 返回困惑度和每个字符平均训练时间
    return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()

Teil der Trainingsschleife, die ein RNN-Modell (Recurrent Neural Network) trainiert. Hier ist eine zeilenweise Erklärung des Codes:

  1. train_epoch(net, train_iter, loss, updater, device, use_random_iter):: Dies ist eine Funktionsdefinition, die zur Durchführung eines Trainingszyklus verwendet wird.

    • net:RNN-Modell.
    • train_iter: Trainingsdaten-Iterator , der zum Generieren von Trainingsdaten-Batches verwendet wird.
    • loss: Verlustfunktion , wird zur Berechnung des Verlusts des Modells verwendet.
    • updater: Parameter-Updater , der der PyTorch-Optimierer (zum Beispiel torch.optim.SGD) oder eine benutzerdefinierte Parameter-Update-Funktion sein kann.
    • device: Trainingsgerät, normalerweise eine GPU.
    • use_random_iter: Ein boolescher Wert, der angibt, ob ein zufälliger Iterator verwendet werden soll .
  2. state, timer = None, dltools.Timer(): Initialisiert stateauf None, timerwird für die Zeitmessung verwendet .

  3. metric = dltools.Accumulator(2): Erstellen Sie einen Akkumulator metriczum Akkumulieren von Verlusten und der Anzahl der Proben , initialisiert als Liste von 2 Elementen.

  4. for X, Y in train_iter:: Durchlaufen Sie den Trainingsdaten-Iterator, um Trainingsdatenstapel Xund -bezeichnungenY zu erhalten .

  5. if state is None or use_random_iter:: Überprüfen Sie state, ob wahr ist Noneoder use_random_iterist. Wenn dies der Fall ist, bedeutet dies, dass der verborgene Zustand des RNN neu initialisiert werden muss.

  6. state = net.begin_state(batch_size=X.shape[0], device=device): Rufen Sie net.begin_statedie Methode auf, um den verborgenen Zustand des RNN-Modells zu initialisieren , einschließlich batch_size(Stapelgröße) und device(Geräte-)Parameter.

  7. y = Y.T.reshape(-1): Transponieren und reduzieren Sie die BezeichnungYy_hat für die Verlustberechnung mit der Modellausgabe .

  8. X, y = X.to(device), y.to(device): Eingabedaten Xund Beschriftungen yauf ein bestimmtes Computergerät verschieben, normalerweise eine GPU.

  9. y_hat, state = net(X, state): Verwenden Sie die Vorwärtsausbreitung des RNN-Modells , um die Modellausgabe y_hatund den aktualisierten verborgenen Zustand zu berechnen state.

  10. l = loss(y_hat, y.long()).mean(): Berechnen Sie den Verlustl . Nehmen Sie dabei an, dass es sich um eine Verlustfunktion handelt loss, die die Modellausgabe y_hatund die Bezeichnung vom Ganzzahltyp akzeptieren kann. yBerechnen Sie dann den Durchschnitt der Verluste.

  11. if isinstance(updater, torch.optim.Optimizer):: Überprüfen Sie updater, ob es sich um den Optimierer von PyTorch handelt . Wenn ja, bedeutet dies, dass der integrierte Optimierer verwendet wird.

  12. updater.zero_grad(): Wenn Sie den PyTorch-Optimierer verwenden , setzen Sie die Farbverläufe auf Null.

  13. l.backward(): Backpropagation , Berechnung des Gradienten.

  14. grad_clippling(net, 1): Rufen Sie grad_clipplingdie Funktion auf , um eine Gradientenbeschneidungsoperation für den Gradienten durchzuführen und eine Gradientenexplosion zu verhindern.

  15. updater.step(): Wenn Sie den PyTorch-Optimierer verwenden , führen Sie den Parameteraktualisierungsschritt durch.

  16. else:: Wenn der PyTorch-Optimierer nicht verwendet wird, bedeutet dies, dass eine benutzerdefinierte Parameteraktualisierungsfunktion verwendet wird.

  17. grad_clipping(net, 1): Führen Sie eine Gradientenbeschneidungsoperation für den Gradienten durch, um eine Gradientenexplosion zu verhindern.

  18. updater(batch_size=1): Parameteraktualisierungsvorgang durchführen und Parameter batch_size=1an den Updater übergeben.

  19. metric.add(l * y.numel(), y.numel()): Addieren Sie den Verlust lmultipliziert mit der Anzahl der Proben in der aktuellen Charge zum y.numel()Akkumulatormetric , um anschließend den durchschnittlichen Verlust zu berechnen.

  20. Gibt Verwirrung und jedes Zeichen zurück

5.1 Zeiterfassung

class Timer:
    """Record multiple running times."""
    def __init__(self):
        self.times = []
        self.start()

    def start(self):
        """Start the timer."""
        self.tik = time.time()

    def stop(self):
        """Stop the timer and record the time in a list."""
        self.times.append(time.time() - self.tik)
        return self.times[-1]

    def avg(self):
        """Return the average time."""
        return sum(self.times) / len(self.times)

    def sum(self):
        """Return the sum of time."""
        return sum(self.times)

    def cumsum(self):
        """Return the accumulated time."""
        return np.array(self.times).cumsum().tolist()

5.2 Akku

class Accumulator:
    """For accumulating sums over `n` variables."""
    def __init__(self, n):
        self.data = [0.0] * n

    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]

    def reset(self):
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]

Die Klasse accumulator( Accumulator) wird verwendet, um ndie Summe von Variablen zu akkumulieren . Im Folgenden sind die Methoden und Eigenschaften dieser Klasse aufgeführt:

  • __init__(self, n): Der Konstruktor der Klasse akzeptiert einen Parameter n, der die Anzahl der zu akkumulierenden Variablen angibt. Während der Initialisierung wird eine Längenliste erstellt, num self.datadie akkumulierten Ergebnisse zu speichern, und die Anfangswerte werden auf festgelegt 0.0.

  • add(self, *args): Diese Methode wird verwendet, um die an sie übergebenen Argumente zu akkumulierenself.data . Es akzeptiert eine beliebige Anzahl von Argumenten und self.dataaddiert den Wert jedes Arguments zum Wert an der entsprechenden Position in . Die Hauptfunktion dieser Methode besteht darin, die Werte mehrerer Variablen zu akkumulieren self.data.

  • reset(self): Diese Methode wird verwendet, um den Akkumulator zurückzusetzen und self.dataalle Werte auf zu setzen 0.0. Dies ist nützlich, wenn Sie eine neue Akkumulationsberechnung starten.

  • __getitem__(self, idx): Diese Methode wird verwendet, um den Wert der entsprechenden Position im Akkumulator über den Indexidx zu erhalten . Es gibt den Wert self.dataam Index in zurück idx.

Die Hauptfunktion dieses Akkumulators besteht darin, bequem mehrere Variablen zu akkumulieren, und der Wert des Akkumulators kann jederzeit zurückgesetzt werden. Dies ist bei einigen statistischen oder kumulativen Berechnungen nützlich. Durch kontinuierliches Aufrufen addder Methode können die Werte mehrerer Variablen im Akkumulator akkumuliert werden, und dann __getitem__wird das akkumulierte Ergebnis über die Methode erhalten. Wenn Sie die Akkumulationsberechnung neu starten müssen, können Sie resetdie Methode aufrufen, um den Wert des Akkumulators auf seinen Anfangszustand zurückzusetzen.

Sechs Trainingscode-Kombinationen

# 组合到一起
def train(net, train_iter, vocab, lr, num_epochs, device, use_random_iter=False):
    loss = nn.CrossEntropyLoss()
    animator = dltools.Animator(xlabel='epoch', ylabel='perlexity', legend=['train'], xlim=[10, num_epochs])
    
    # 初始化
    if isinstance(net, nn.Module):
        updater = torch.optim.SGD(net.parameters(), lr)
    else:
        updater = lambda batch_size: dltools.sgd(net.params, lr, batch_size)
    
    pred = lambda prefix: predict(prefix, 50, net, vocab, device)
    # train and forecast
    for epoch in range(num_epochs):
        ppl, speed = train_epoch(net, train_iter, loss, updater, device, use_random_iter)
        
        if (epoch + 1) % 10 == 0:
            print(pred('time traveller'))
            animator.add(epoch + 1, [ppl])
    print(f'困惑度{ppl:.1f}, {speed: .1f} 词元/秒{str(device)}')
    print(pred('time traveller'))
    print(pred('traveller'))

Für das Training eines Recurrent Neural Network (RNN)-Modells wird eine Trainingsschleife definiert . Hier sind die wichtigsten Schritte und Funktionen des Codes:

  1. Importieren Sie die erforderlichen Bibliotheken, einschließlich PyTorch-Module und benutzerdefinierte dltools-Module.

  2. Eine Verlustfunktion wird lossmithilfe der Kreuzentropieverlustfunktion ( nn.CrossEntropyLoss()) definiert. Kreuzentropie wird häufig bei Textklassifizierungsproblemen verwendet.

  3. Erstellt ein Objekt zur Visualisierung des Trainingsprozesses animator. Mit diesem Objekt kann die Verlustkurve während des Trainings aufgezeichnet werden.

  4. Initialisieren Sie den Modellparameter-Updaterupdater . Wenn es netsich um ein PyTorch-Modell ( nn.ModuleTyp) handelt, verwenden Sie den SGD-Optimierer (Stochastic Gradient Descent), um die Modellparameter zu aktualisieren. Andernfalls verwenden Sie eine benutzerdefinierte Gradientenabstiegsfunktion, dltools.sgdum die Modellparameter zu aktualisieren.

  5. Definiert eine Funktion pred, die anhand eines vorangestellten Textes Vorhersagen generiert . Diese Funktion ruft predictdie Funktion auf, um Vorhersagen für den Text zu generieren .

  6. Führen Sie einen Trainingszyklus durch . Die Anzahl der Zyklen beträgt num_epochs. In jeder Epoche train_epochwird die Funktion aufgerufen, um das Modell zu trainieren und die Perplexität und Trainingsgeschwindigkeit zu berechnen .

  7. Wenn die Sequenznummer ( epoch + 1) der aktuellen Epoche ein Vielfaches von 10 ist, rufen Sie preddie Funktion auf, um Textvorhersagen mit dem Präfix „Zeitreisender“ zu generieren, und fügen Sie zur animatorVisualisierung die Perplexität hinzu.

  8. Geben Sie die Ratlosigkeit, Trainingsgeschwindigkeit und Geräteinformationen der aktuellen Epoche aus .

  9. Abschließend werden Textvorhersagen für „Zeitreisender“ und „Reisender“ generiert und ausgegeben .

Insgesamt implementiert dieser Code eine Trainingsschleife zum Trainieren eines RNN-Modells und zum Generieren von Textvorhersagen während des Trainings. Gleichzeitig wird auch ein benutzerdefiniertes dltools-Modul verwendet, um den Trainingsprozess und die Visualisierung zu verwalten.

6.1 Echtzeit-Zeichenwerkzeuge

class Animator:
    """For plotting data in animation."""
    def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
                 ylim=None, xscale='linear', yscale='linear',
                 fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
                 figsize=(3.5, 2.5)):
        # Incrementally plot multiple lines
        if legend is None:
            legend = []
        dltools.use_svg_display()
        self.fig, self.axes = dltools.plt.subplots(nrows, ncols, figsize=figsize)
        if nrows * ncols == 1:
            self.axes = [self.axes,]
        # Use a lambda function to capture arguments
        self.config_axes = lambda: dltools.set_axes(self.axes[
            0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
        self.X, self.Y, self.fmts = None, None, fmts

    def add(self, x, y):
        # Add multiple data points into the figure
        if not hasattr(y, "__len__"):
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"):
            x = [x] * n
        if not self.X:
            self.X = [[] for _ in range(n)]
        if not self.Y:
            self.Y = [[] for _ in range(n)]
        for i, (a, b) in enumerate(zip(x, y)):
            if a is not None and b is not None:
                self.X[i].append(a)
                self.Y[i].append(b)
        self.axes[0].cla()
        for x, y, fmt in zip(self.X, self.Y, self.fmts):
            self.axes[0].plot(x, y, fmt)
        self.config_axes()
        display.display(self.fig)
        display.clear_output(wait=True)

6.2nn.CrossEntropyLoss

nn.CrossEntropyLoss()ist die Verlustfunktion, die in PyTorch für Klassifizierungsprobleme mit mehreren Kategorien verwendet wird . Beim Deep Learning wird die Kreuzentropieverlustfunktion häufig verwendet, um den Unterschied zwischen der Modellausgabe und dem tatsächlichen Ziel zu messen, insbesondere bei Klassifizierungsaufgaben.

Konkret nn.CrossEntropyLoss()wird der Verlust für Klassifizierungsaufgaben mit mehreren Kategorien wie folgt berechnet:

Angenommen, es gibt CKategorien (Anzahl der Kategorien). Für jede Stichprobe gibt das Modell einen Vektor mit CElementen aus. Jedes Element stellt die Wahrscheinlichkeitsbewertung der Stichprobe dar, die zur entsprechenden Kategorie gehört. Dieser Vektor wird oft als „Logits“ bezeichnet.

  • nn.CrossEntropyLoss()Zunächst werden die Logits des Modells mithilfe der Softmax-Funktion in Wahrscheinlichkeitsverteilungen umgewandelt. Die Softmax-Funktion ordnet Logits einer Wahrscheinlichkeitsverteilung zu, sodass die Summe der Wahrscheinlichkeiten aller Klassen 1 ist.
  • Als nächstes kodiert es die tatsächliche Kategoriebezeichnung (Ground Truth) in einen One-Hot-Vektor, in dem nur ein Element 1 ist, was die wahre Kategorie der Stichprobe angibt.
  • Schließlich wird die Kreuzentropie der Wahrscheinlichkeitsverteilung der Modellausgabe mit der tatsächlichen Klasse als Verlustwert berechnet. Je kleiner die Kreuzentropie, desto näher liegen die Vorhersagen des Modells an der wahren Bezeichnung.

nn.CrossEntropyLoss()Die Parameter von sind normalerweise die Modellausgaben (Logits) und die tatsächlichen Klassenbezeichnungen. Beim Training eines neuronalen Netzwerks passt der Optimierer mithilfe des Backpropagation-Algorithmus die Parameter des Modells an, um den Kreuzentropieverlust zu minimieren und dadurch die Klassifizierungsleistung des Modells zu verbessern.

7. Überprüfen Sie die Trainingsergebnisse

num_epochs, lr = 200, 0.01
# 使用顺序抽样
train(net, train_iter, vocab, lr, num_epochs, dltools.try_gpu())

Gut !

Acho que você gosta

Origin blog.csdn.net/March_A/article/details/132789749
Recomendado
Clasificación