Repository navigation
Non trivial slice assignments and tensor manipulation #935
Copy link
Copy link
Closed
Labels
Description
Hello,
I am trying to port Retinaface (face and landmark detection in pytorch) to rust and was just wondering if you support operations similar to
def decode(loc, priors, variances):
"""Decode locations from predictions using priors to undo
the encoding we did for offset regression at train time.
Args:
loc (tensor): location predictions for loc layers,
Shape: [num_priors,4]
priors (tensor): Prior boxes in center-offset form.
Shape: [num_priors,4].
variances: (list[float]) Variances of priorboxes
Return:
decoded bounding box predictions
"""
boxes = torch.cat((
priors[:, :2] + loc[:, :2] * variances[0] * priors[:, 2:],
priors[:, 2:] * torch.exp(loc[:, 2:] * variances[1])), 1)
boxes[:, :2] -= boxes[:, 2:] / 2
boxes[:, 2:] += boxes[:, :2]
return boxesI couldn't find similar functionalities in your library to implement operations like boxes[:, :2] -= boxes[:, 2:] / 2
This is my current implementation which compiles but is still not tested for correctness and is not optimized.
pub fn decode(
loc: ArrayBase<OwnedRepr<f32>, Dim<[usize; 2]>>,
priors: ArrayBase<OwnedRepr<f32>, Dim<[usize; 2]>>,
variances: &Vec<f32>
) -> Result<ArrayBase<OwnedRepr<f32>, Dim<[usize; 2]>>, Error>
{
let priors_to = priors.clone().slice_move(s![.., ..2]);
let priors_from = priors.slice_move(s![.., 2..]);
let mut loc_to = loc.clone().slice_move(s![.., ..2]);
let mut loc_from = loc.slice_move(s![.., 2..]);
loc_to = loc_to.mapv(|v:f32| v * variances[0]);
loc_from = loc_from.mapv(|v:f32| (v * variances[1]).exp());
let a =
priors_to + loc_to * priors_from.clone();
let b =
priors_from * loc_from;
let mut boxes = stack![Axis(1), a, b];
let boxes_to = boxes.clone().slice_move(s![.., ..2]);
let mut boxes_from = boxes.clone().slice_move(s![.., 2..]);
boxes_from = boxes_from.mapv(|v: f32| v / 2.0);
for i in 0..boxes.shape()[0] {
for j in 0..2 {
boxes[[i, j]] -= boxes_from[[i, j]];
}
}
for i in 0..boxes.shape()[0] {
for j in 0..2 {
boxes[[i, j]] += boxes_to[[i, j]];
}
}
Ok(boxes)
}Also is it possible to stack multiple tensors like:
landms = torch.cat((priors[:, :2] + pre[:, :2] * variances[0] * priors[:, 2:],
priors[:, :2] + pre[:, 2:4] * variances[0] * priors[:, 2:],
priors[:, :2] + pre[:, 4:6] * variances[0] * priors[:, 2:],
priors[:, :2] + pre[:, 6:8] * variances[0] * priors[:, 2:],
priors[:, :2] + pre[:, 8:10] * variances[0] * priors[:, 2:],
), dim=1)
Or i have to go 2 by 2 and produce same results?
Reactions are currently unavailable
Activity
xd009642 commentedon Mar 11, 2021
So it looks like slicing is what you're looking for. I just knocked up a quick example of adding the elements in a subview of a matrix.
That's for your example where you add boxes and boxes together. For another one you can do the equivalent slice and then I believe jsomething like
boxes.slice_mut(s![0, ..]) += &boxesto.slice(s![0,..]);, though I haven't knocked up an example to try itvladimirmujagic commentedon Mar 11, 2021
I see i was trying
mapv_inplacebut couldn't make it to work probably because of my limited rust knowledge.Thank you very much i will try it out.
jturner314 commentedon Mar 12, 2021
These are a couple of implementations for
decode. Please test them before using. I've checked that they compile, but not that they're correct.You could use slicing instead of
.view().split_at()if you find it easier to read. Personally, I finddecode2a little easier to read thandecode1. Additionally, it has only a single allocation for the output array, compared to four allocations indecode1. I'd also consider changing the type ofvariancesto[f32; 2]if you know that it's always going to have exactly two elements.For the second question, you could use slicing and
concatenate, although the code does seem somewhat repetitive and creates a lot of temporary allocations. Something like this is a bit nicer, IMO:Edit: Or, you could do this, which is more concise but creates a temporary allocation in each iteration of the
forloop:I wonder if you'd be happier using arrays with more axes. For example, I wonder if it would make sense for your use-case for
preandlandmsto be three-dimensional rather than concatenating things along axis 1.vladimirmujagic commentedon Mar 12, 2021
Wow... thank you this explains a lot and has great educational value for me. I will test and try to understand these approaches and get back to you. However final test probably wont be possible until i translate all the code. Not easy to compare with python implementation i have to hardcode values + torch is not using fixed input size like onnxruntime i am using in rust.
I am pretty close to glue everything up, got a few more numpy postprocessing lines to go.
Currently here: https://github.com/biubug6/Pytorch_Retinaface/blob/master/detect.py#L121
So got two more functions to resolve
np.where, np.argsortI guess those are not implemented directly yet, how do you workaround them?jturner314 commentedon Mar 12, 2021
So, you want to implement the following lines?
What are the shapes of
scores,boxes, andlandms?vladimirmujagic commentedon Mar 13, 2021
Yes, those are the remaining lines to finish the algorithm + a few more but those shouldn't be a problem pretty straightforward slicing operations.
Regarding the shapes, this is output of original implementation:
So basically they are
2D,2D,1D, numbers4and10are fixed and independent of network input size. Number26240depends on the input size, in my case i am using640x640onnx model so I have16800.vladimirmujagic commentedon Mar 13, 2021
Pushed current code including your implementations: https://github.com/vladimirmujagic/rust-retinaface
jturner314 commentedon Mar 13, 2021
Here are a couple of ways to do it:
I'm not sure which will be faster. The second approach has an extra allocation, but the sorting should be faster because it doesn't have to randomly index into the
scoresarray. Note also that if you don't needtop_k_indto be sorted, then you could useselect_nth_unstable_byinstead ofsort_unstable_byto avoid a full sort, although the unordered indices may slow down the final.select()calls. If the initialfilter+collectis slow, it might be a little faster to callpushrepeatedly infor_each; I'm not sure.vladimirmujagic commentedon Mar 14, 2021
Added your code to current flow and tested for correctness by comparing original python implementation and its outputs to rust implementation.
Here is the current postprocessing flow:
Code is compiling but produces unexpected error for given thresholds:
Error:
So this implies if i am not mistaken that filtering operation:
discards too many values which further implies that network outputs might be wrong (delta is too big), this is still not confirmed and shouldn't be the case even though original implementation is using
pytorchand in rust implementationonnxruntimeis used because i am passing exactly the same image as network input and there were no issues when switching between inference engines in python.Still trying to understand if this can somehow be related to incorrect tensor manipulations
jturner314 commentedon Mar 14, 2021
Yeah, if that panic occurs on the line
let top_k_ind = &ind_above_thresh[..top_k];, thenind_above_threshhas length 277, which means that 277 scores are aboveconfidence_threshold. It sounds like this is less than what you were expecting. The bug could be in the code I suggested or somewhere else. I'd suggest testing smaller pieces of the code separately to narrow down where the problem is located.bluss commentedon Mar 15, 2021
The course of this issue is just a discussion I guess? I could move it to discussions, and using discussions is also a good idea (but I realize they might be less visible).
jturner314 commentedon Mar 15, 2021
Yeah, it's just a discussion. I didn't realize that it was possible to convert issues to discussions. I'll try it.