Background and motivation
We have exposed lots of functions we can use with Tensor<T>. These currently all operate on the entire backing memory of the Tensor. The problem with this is that many times you want to do the operation on whole dimensions, rather than the whole data, and we have no easy way of providing this currently. For a very simple example say your Tensor<T> is 2d, think of it like an Excel table. In Excel we can sum the whole table, but we can also sum each row, for example. This is the functionality we need to add to Tensor<T>.
The approach we have taken means we don't have to add overloads to every operation that could do this type of behavior. Currently we are planning on these methods needing this behavior.
CosineSimilarity
StdDev
Average
Max
MaxMagnitude
MaxMagnitudeNumber
MaxNumber
Min
MinMagnitude
MinMagnitudeNumber
MinNumber
Norm
Product
SoftMax
Sum
SumOfSquares
IndexOfMax
IndexOfMaxMagnitude
IndexOfMin
IndexOfMinMagnitude
API Proposal
namespaceSystem.Numerics.Tensors;publicinterfaceIReadOnlyTensor<TSelf,T>:IEnumerable<T>whereTSelf:IReadOnlyTensor<TSelf,T>{//used to easily get a sub dimension of a tensor. Like all the Rows of a tensor for example.TSelfSliceAlongDimension(intdimension,nintindex);}publicstaticpartialclassTensor{// These are for things like Sum that take in a tensor and return a single T.publicdelegateTTensorAggregationPredicate<T>(inReadOnlyTensorSpan<T>tensor);publicstaticTensor<T>AggregateMany<T>(thisTensor<T>tensor,TensorAggregationPredicate<T>func,intdimension=-1);// These are for things like CosineSimilarity that take in a tensor and return a new tensor as well.publicdelegateTensor<T>TensorSelectionPredicate<T>(inReadOnlyTensorSpan<T>tensor);publicstaticTensor<T>SelectMany<T>(thisTensor<T>tensor,TensorSelectionPredicate<T>func,intdimension=-1);publicstaticTensor<T>ToTensor<T>(thisIEnumerable<Tensor<T>>source);}publicsealedclassTensor<T>:ITensor<Tensor<T>,T>{publicDimensionCollectionDimensions{get;}publicsealedclassDimensionCollection:System.Collections.Generic.ICollection<Tensor<T>>,System.Collections.Generic.IEnumerable<Tensor<T>>,System.Collections.Generic.IReadOnlyCollection<Tensor<T>>,System.Collections.ICollection{publicintCount{get;}publicvoidCopyTo(Tensor<T>[]array,intindex)publicEnumeratorGetEnumerator();//All else explicitly implemented and match what Dictionary does publicstructEnumerator:IEnumerator<Tensor<T>>{internalEnumerator(Tensor<T>tensor,intdimension);publicboolMoveNext();publicvoidReset();publicvoidDispose();Tensor<T>IEnumerator<Tensor<T>>.Current;object?IEnumerator.Current;}}}publicreadonlyrefstructReadOnlyTensorSpan<T>{publicDimensionCollectionDimensions{get;}publicrefstructDimensionCollection:System.Collections.Generic.ICollection<Tensor<T>>,System.Collections.Generic.IEnumerable<Tensor<T>>,System.Collections.Generic.IReadOnlyCollection<Tensor<T>>,System.Collections.ICollection{publicintCount{get;}publicvoidCopyTo(Tensor<T>[]array,intindex)publicEnumeratorGetEnumerator();//All else explicitly implemented and match what Dictionary does publicrefstructEnumerator:IEnumerator<Tensor<T>>{internalEnumerator(Tensor<T>tensor,intdimension);publicboolMoveNext();publicref readonly ReadOnlyTensorSpan<T>Current;}}}publicrefstructTensorSpan<T>{publicDimensionCollectionDimensions{get;}publicrefstructDimensionCollection:System.Collections.Generic.ICollection<Tensor<T>>,System.Collections.Generic.IEnumerable<Tensor<T>>,System.Collections.Generic.IReadOnlyCollection<Tensor<T>>,System.Collections.ICollection{publicintCount{get;}publicvoidCopyTo(Tensor<T>[]array,intindex)publicEnumeratorGetEnumerator();//All else explicitly implemented and match what Dictionary does publicrefstructEnumerator:IEnumerator<Tensor<T>>{internalEnumerator(Tensor<T>tensor,intdimension);publicboolMoveNext();publicrefTensorSpan<T>Current;}}}API Usage
Tensor<int>t1=Tensor.Create([1,2,3,4,5,6,7,8,9],[3,3]);// Will be [1,2,3]Tensor<int>slice=t1.SliceAlongDimension(0,0);// Will be [7, 8, 9]slice=t1.SliceAlongDimension(0,2);for(inti=0;i<3;i++){// Get each slicet1.SliceAlongDimension(0,i);// Can now do something which each sub slice.}Tensor<int>tensor=Tensor.Create([1,2,3,4],[2,2]);// Sum tensor will be [3, 7]Tensor<int>sum=t.AggregateMany(t =>Sum(t),0);// CosineTensor would be [1,1,1]Tensor<float>tensor1=Create<float>([1,2,3,4,5,6,7,8,9],[3,3],[],false);Tensor<float>cosineTensor=tensor1.SelectMany(t =>CosineSimilarity<T>(t,t),0);Alternative Designs
We could explicitly overload every method that has this behavior. That would mean an additional 20 overloads just for now. As time goes on this number would grow. If a user wanted to do this with a method we hadn't overloaded yet, they would either have to do it themselves (which we have had happen recently), or they would have to wait. Our current approach allows us to avoid both of these problems.
Risks
Low risk because it's a preview type and its all new features.
Background and motivation
We have exposed lots of functions we can use with
Tensor<T>. These currently all operate on the entire backing memory of theTensor. The problem with this is that many times you want to do the operation on whole dimensions, rather than the whole data, and we have no easy way of providing this currently. For a very simple example say yourTensor<T>is 2d, think of it like an Excel table. In Excel we can sum the whole table, but we can also sum each row, for example. This is the functionality we need to add toTensor<T>.The approach we have taken means we don't have to add overloads to every operation that could do this type of behavior. Currently we are planning on these methods needing this behavior.
CosineSimilarity
StdDev
Average
Max
MaxMagnitude
MaxMagnitudeNumber
MaxNumber
Min
MinMagnitude
MinMagnitudeNumber
MinNumber
Norm
Product
SoftMax
Sum
SumOfSquares
IndexOfMax
IndexOfMaxMagnitude
IndexOfMin
IndexOfMinMagnitude
API Proposal
API Usage
Alternative Designs
We could explicitly overload every method that has this behavior. That would mean an additional 20 overloads just for now. As time goes on this number would grow. If a user wanted to do this with a method we hadn't overloaded yet, they would either have to do it themselves (which we have had happen recently), or they would have to wait. Our current approach allows us to avoid both of these problems.
Risks
Low risk because it's a preview type and its all new features.