Formas de tensor, armazenamento e diferenças do NumPy

O pacote System.Numerics.Tensors fornece Tensor<T>, TensorSpan<T> e ReadOnlyTensorSpan<T> para dados multidimensionais. Os formatos de tensores descrevem as dimensões lógicas; os strides descrevem a distância, em elementos, entre posições sucessivas ao longo de cada dimensão.

O comportamento descrito aqui vem no pacote em seus frameworks de destino com suporte. A atualização do pacote pode afetar aplicativos destinados a versões de .NET anteriores.

Esses tipos compartilham muitas convenções com o NumPy, mas não são intercambiáveis. Leve em conta as seguintes diferenças ao portar um algoritmo.

Formas vazias e resultados escalares

Quando você cria um tensor de uma forma vazia ([]) sem dados subjacentes, o tensor tem forma [0] e nenhum elemento. No NumPy, o formato () tem classificação zero e contém um elemento escalar. Em particular, np.empty(()) aloca um escalar com um valor não inicializado; "vazio" não significa que não tenha elementos.

Por outro lado, quando você omite comprimentos de um construtor de tensor-span sobre dados existentes, o construtor infere uma forma unidimensional do array ou span fornecido.

Quando você espreme um tensor com forma [1, 1], o resultado tem forma [1], não a forma rank-zero do NumPy (). Ambos os resultados contêm um elemento e podem ser transmitidos como um escalar, mas o código que examina a classificação, seleciona eixos ou indexa o resultado deve levar em conta a dimensão retida.

Quando os strides são omitidos, qualquer formato que contenha uma dimensão de comprimento zero terá strides zero em todas as dimensões. Sua contagem de elementos e o armazenamento necessário são zero, independentemente dos outros comprimentos de dimensão. Os comprimentos negativos permanecem inválidos. Os strides explícitos ainda devem satisfazer a validação de layout.

Strides e dados invertidos

Os passos devem ser nãonegativos. O NumPy pode representar uma visão invertida com um stride negativo, por exemplo, array[::-1].

Para inverter o primeiro eixo, chame Tensor.ReverseDimension com dimensão 0:

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);

Os elementos lógicos de reversed são [3, 4, 1, 2]. A sobrecarga de alocação produz um novo tensor; não é uma visão com stride negativo sobre o armazenamento original.

Redimensionar um tensor

Tensor.Resize e Tensor.ResizeTo preenchem elementos lógicos adicionados com default(T). Por exemplo, se você aumentar [1, 2] para cinco int elementos, o resultado conterá [1, 2, 0, 0, 0]. O np.resize do NumPy, em vez disso, repete a entrada e produz [1, 2, 1, 2, 1].

Se o destino for menor, a operação descartará elementos após a contagem de elementos lógicos do destino. Se for maior, a operação inicializará os elementos de destino lógicos restantes mesmo quando o destino tiver lacunas entre os elementos. O armazenamento fora do destino lógico não faz parte da operação de redimensionamento.

Tensor.ResizeTo e a concatenação em um destino existente rejeitam uma dimensão de passo zero que contém mais de um elemento lógico. Tal dimensão faz com que várias posições de saída se refiram ao mesmo armazenamento e não pode conter valores de saída distintos. Strides zero em dimensões singleton e destinos vazios não criam esse conflito.

Fontes e destinos sobrepostos

Objetos tensores separados e intervalos podem se referir a regiões sobrepostas do mesmo array. Os casos com suporte dependem da operação e do layout:

Operação Sobreposição suportada
CopyTo e TryCopyTo Origem e destino densos com contagens de elementos iguais ou visualizações exatamente idênticas. Cópias densas usam cópia de intervalos segura para sobreposição.
FlattenTo e TryFlattenTo Uma origem densa e o span de destino.
Operações por elemento com um destino Uma entrada e uma saída com a mesma referência inicial, comprimentos e espaçamentos, desde que a visualização não tenha locais de armazenamento lógico repetidos. Cada entrada é verificada independentemente.
Tensor.ResizeTo Origem e destino densos. A sobreposição com stride sem suporte é rejeitada.
Inversão para a mesma visualização Uma exibição densa pode ser invertida no próprio lugar. Inversões sem operação também são suportadas.

Outros layouts sobrepostos sem suporte lançam ArgumentException antes de a operação gravar a saída. Um tensor denso sozinho não garante suporte para cada operação: por exemplo, um destino denso deslocado tem suporte para uma cópia, mas não para uma operação elemento a elemento.

Os métodos TryCopyTo e TryFlattenTo retornam false sem copiar quando o destino é muito curto. TryCopyTo também retorna false para formas de origem e destino incompatíveis. Para formas compatíveis com capacidade de destino suficiente, esses métodos ainda podem lançar exceções para sobreposição não suportada; Try não suprime exceções de layout inválido.

Por exemplo, a seguinte origem com stride e destino denso se sobrepõem:

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 });

Para copiar desse layout sobreposto sem suporte, primeiro faça um snapshot no armazenamento independente:

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

O instantâneo contém [1, 2, 4, 5]. A cópia grava esses elementos no destino sem alterar os valores originais antes de serem lidos.

Para uma operação elementwise que recebe um destino, aloque um tensor de resultado separado ou faça um snapshot de cada entrada sobreposta antes de gravar a saída. Para um destino de difusão com locais de armazenamento repetidos, use o armazenamento de saída independente; um instantâneo de entrada não torna esse destino capaz de manter valores distintos.

A biblioteca não aloca implicitamente um instantâneo de tensor completo para dar suporte a layouts arbitrários sobrepostos. Cópias com stride não sobrepostas não exigem esse armazenamento temporário.

Consulte também