swift-apis
swift-apis copied to clipboard
Tensor(randomUniform: ...) does not behave as documented
The documentation for Tensor<Float>(randomUniform:lowerBound:upperBound:seed:) says
Creates a tensor with the specified shape, randomly sampling scalar values from a uniform distribution between lowerBound and upperBound.
However, I have found that the shape only matches the one specified when the bounds are 0D tensors. The documentation makes no mention of this being a precondition.
The equivalent Python function has the same restriction, so perhaps this is the expected behaviour and the documentation just needs to be amended to specify it.
I had hoped that using a tensor of bounds values would produce a tensor where each value was within the corresponding bounds. That is, Tensor<Float>(randomUniform: [3], lowerBound: [0, 1, 2], upperBound: [1, 2, 3]) would give a random tensor [0..<1, 1..<2, 2..<3]. My actual use case is 3D rather than 1D.
The desired behaviour can be achieved pretty easily.
let lb = Tensor<Float>([0, 1, 2])
let ub = Tensor<Float>([1, 2, 3])
let random = lb + (ub - lb) * Tensor(randomUniform: [3])
I would suggest that ideally this should behave as I'd like it to, but if not the documentation needs to be amended to make the preconditions clear, and that the code should also have preconditions, which would make this another item to add to the list in #517.
I had hoped that using a tensor of bounds values would produce a tensor where each value was within the corresponding bounds.
Does using scalar lower/upper bounds solve your use case?
import TensorFlow
print(Tensor(randomUniform: [3, 3], lowerBound: Tensor(-1), upperBound: Tensor(1)))
// [[ 0.8018838141398685, 0.5886823980640088, -0.9940630515487747],
// [ 0.9104622604107688, -0.2710584578836728, 0.08515388234458765],
// [-0.31065717882801147, -0.7031103329154349, -0.8709943926625416]]
I hadn't even thought about non-scalar Tensor lower/upper bounds before, that seems like an advanced use case.
No it doesn't, I specifically need these individual ranges. I've solved it using the generate in 0..<1 then scale and shift technique for now, so this isn't blocking me, but it's a bit clunky.
This probably is a pretty unusual use case. I'm training against generated data, and this is part of my generation step. I first randomly generate some sample ranges, then have to randomly generate some points within those ranges, and the ranges are all different sizes.
I've got quite a lot of advanced scenarios in this project, with things like random access tensor based spline integration going on.
It would be nice to have an overload that takes Float instead of the 0D tensor in the scalar case too.
As it's an unusual case, I thought an example might be interesting, so this is a simplified version of my actual code.
// Demo, actual values come from config
let containerLowerBound: Float = -512
let containerUpperBound: Float = 512
let containerSize = containerUpperBound - containerLowerBound
let margin: Float = 16
let minRegionLength: Float = 16
let maxRegionLength = containerSize - 2 * margin
let batchSize = Int(pow(2, 10))
let regionCount = Int(pow(2, 10))
// Generate uniform region lengths that all fit within the container, allowing a margin.
let regionLengths = Tensor<Float>(
randomUniform: [batchSize, regionCount],
lowerBound: Tensor(minRegionLength),
upperBound: Tensor(maxRegionLength)
)
// Generate uniform region start positions such that the region fits within the container, allowing a margin.
// Coordinate space local to container.
let regionStartPositions = margin + (maxRegionLength - regionLengths) * Tensor<Float>(
randomUniform: [batchSize, regionCount]
)
I would like that last bit to be able to be this instead.
// Generate uniform region start positions such that the region fits within the container, allowing a margin.
// Coordinate space local to container.
let regionStartPositions = Tensor<Float>(
randomUniform: [batchSize, regionCount],
lowerBound: Tensor(margin),
upperBound: (containerSize - margin) - regionLengths
)
That's rewritten from my real code and untested, so there might be the odd little maths mistake in there, but the idea should be clear.