Tensor-Shapes, Speicher- und NumPy-Unterschiede

Das System.Numerics.Tensors-Paket stellt Tensor<T>, TensorSpan<T> und ReadOnlyTensorSpan<T> für mehrdimensionale Daten bereit. Tensor-Shapes beschreiben die logischen Dimensionen; Strides beschreiben den Abstand in Elementen zwischen aufeinanderfolgenden Positionen entlang jeder Dimension.

Das hier beschriebene Verhalten ist im Paket in den unterstützten Zielframeworks enthalten. Das Aktualisieren des Pakets kann sich auf Apps auswirken, die auf frühere .NET Versionen abzielen.

Diese Typen teilen viele Konventionen mit NumPy, sind aber nicht austauschbar. Berücksichtigen Sie beim Portieren eines Algorithmus die folgenden Unterschiede.

Leere Shapes und skalare Ergebnisse

Wenn Sie einen Tensor aus einem leeren Shape ([]) ohne zugrunde liegende Daten erstellen, hat der Tensor Shape [0] und keine Elemente. In NumPy weist die Form () rang null auf und enthält ein skalares Element. Insbesondere weist np.empty(()) einen Skalar mit einem nicht initialisierten Wert zu; "empty" bedeutet nicht, dass es keine Elemente enthält.

Im Gegensatz dazu leitet der Konstruktor eine eindimensionale Form aus dem angegebenen Array oder einem Span ab, wenn Sie Längen von einem Tensor-Span-Konstruktor über vorhandene Daten weglassen.

Wenn Sie einen Tensor mit Form [1, 1]zusammendrücken, hat das Ergebnis Form [1], nicht NumPys Rang-Null-Form (). Beide Ergebnisse enthalten ein Element und können als Skalar broadcastet werden, aber Code, der den Rang untersucht, Achsen auswählt oder das Ergebnis indiziert, muss die beibehaltene Dimension berücksichtigen.

Wenn Strides weggelassen werden, weist jedes Shape, das eine Dimension der Länge null enthält, in jeder Dimension Nullstrides auf. Die Elementanzahl und der erforderliche Speicher sind null, unabhängig von den anderen Dimensionslängen. Negative Längen bleiben ungültig. Explizite Strides müssen weiterhin die Layoutvalidierung erfüllen.

Strides und invertierte Daten

Strides müssen nichtegativ sein. NumPy kann eine umgekehrte Ansicht mit einem negativen Schritt darstellen, z. B array[::-1]. .

Rufen Sie Tensor.ReverseDimension mit Dimension 0 auf, um die erste Achse umzukehren:

using System.Numerics.Tensors;

Tensor<int> source = Tensor.Create(new[] { 1, 2, 3, 4 }, new nint[] { 2, 2 });
Tensor<int> reversed = Tensor.ReverseDimension<int>(source.AsReadOnlyTensorSpan(), 0);

Die logischen Elemente von reversed sind [3, 4, 1, 2]. Die allokierende Überladung erzeugt einen neuen Tensor; es handelt sich nicht um eine Negative-Stride-Ansicht über den ursprünglichen Speicher.

Größe eines Tensors ändern

Tensor.Resize und Tensor.ResizeTo füllen hinzugefügte logische Elemente mit default(T). Wenn Sie beispielsweise int auf fünf [1, 2] Elemente erweitern, enthält das Ergebnis [1, 2, 0, 0, 0]. NumPys np.resize wiederholt stattdessen die Eingabe und erzeugt [1, 2, 1, 2, 1].

Wenn das Ziel kleiner ist, verwirft der Vorgang Elemente nach der logischen Elementanzahl des Ziels. Wenn dieser größer ist, initialisiert der Vorgang die verbleibenden logischen Zielelemente, auch wenn das Ziel Lücken zwischen Elementen aufweist. Der Speicherplatz außerhalb des logischen Ziels ist nicht Teil des Größenänderungsvorgangs.

Tensor.ResizeTo und Verkettung in ein vorhandenes Ziel lehnen eine Null-Stride-Dimension ab, die mehr als ein logisches Element enthält. Eine solche Dimension bewirkt, dass mehrere Ausgabepositionen auf denselben Speicher verweisen und keine unterschiedlichen Ausgabewerte enthalten können. Null-Schrittweiten in Singleton-Dimensionen und leere Ziele verursachen diesen Konflikt nicht.

Überlappende Quellen und Ziele

Separate Tensorobjekte und Spans können auf überlappende Bereiche desselben Arrays verweisen. Unterstützte Fälle hängen sowohl vom Vorgang als auch vom Layout ab:

Operation Unterstützte Überschneidung
CopyTo und TryCopyTo Quelle und Ziel sind zusammenhängend und haben die gleiche Elementanzahl oder sind exakt identische Ansichten. Dichte Kopien verwenden überlappungssicheres Kopieren von Speicherbereichen.
FlattenTo und TryFlattenTo Eine dichte Quelle und der Zielbereich.
Elementweise Operationen mit einem Ziel Eine Eingabe und Ausgabe mit demselben Anfangsverweis, längen und strides, sofern die Ansicht keine wiederholten logischen Speicherorte aufweist. Jede Eingabe wird unabhängig voneinander überprüft.
Tensor.ResizeTo Quelle und Ziel dicht beieinander Nicht unterstützter strided overlap wird abgelehnt.
Umkehr in dieselbe Ansicht Eine dichte Ansicht kann direkt umgekehrt werden. Es werden auch No-Op-Reversals unterstützt.

Andere nicht unterstützte überlappende Layouts lösen ArgumentException aus, bevor der Vorgang die Ausgabe schreibt. Ein dichter Tensor allein garantiert nicht die Unterstützung für jeden Vorgang: Beispielsweise wird ein verschobenes dichtes Ziel für eine Kopie unterstützt, aber nicht für einen elementweisen Vorgang.

Die TryCopyTo und TryFlattenTo Methoden geben false ohne Kopieren zurück, wenn das Ziel zu kurz ist. TryCopyTo gibt auch false für inkompatible Quell- und Ziel-Shapes zurück. Für kompatible Shapes mit ausreichender Zielkapazität können diese Methoden weiterhin bei nicht unterstützten Überlappungen eine Ausnahme auslösen. Try unterdrückt keine Ausnahmen für ungültiges Layout.

Beispielsweise überlappen sich die folgende Quelle mit Schrittweite und das dichte Ziel:

int[] storage = { 1, 2, 3, 4, 5, 6 };
ReadOnlyTensorSpan<int> source = new(storage, new nint[] { 2, 2 }, new nint[] { 3, 1 });
TensorSpan<int> destination = new(storage, new nint[] { 2, 2 });

Um aus diesem nicht unterstützten überlappenden Layout zu kopieren, erstellen Sie zunächst eine Momentaufnahme in unabhängigem Speicher:

int[] snapshot = new int[checked((int)source.FlattenedLength)];
source.FlattenTo(snapshot);
ReadOnlyTensorSpan<int> independentSource = new(snapshot, source.Lengths);
independentSource.CopyTo(destination);

Die Momentaufnahme enthält [1, 2, 4, 5]. Der Kopiervorgang schreibt diese Elemente in das Ziel, ohne die ursprünglichen Werte zu ändern, bevor sie gelesen werden.

Weisen Sie für eine elementweise Operation mit Zielargument einen separaten Ergebnis-Tensor oder eine Momentaufnahme jeder überlappenden Eingabe zu, bevor Sie die Ausgabe schreiben. Verwenden Sie für ein Broadcast-Ziel mit wiederholten Speicherorten stattdessen einen unabhängigen Ausgabe-Speicher; ein Eingabe-Snapshot befähigt dieses Ziel nicht dazu, unterschiedliche Werte zu halten.

Die Bibliothek weist nicht implizit einen vollständigen Tensor-Snapshot zu, um beliebige überlappende Layouts zu unterstützen. Nicht überlappende Kopien mit Schrittweite erfordern keinen temporären Speicher.

Siehe auch