问题
I am trying to perform linear interpolation in MATLAB using a specific precision. I was wondering if there is an efficient way to write the linear interpolation function in MATLAB so that it doesn't require for-loops and runs very fast?
I want to modify the incoming data to a specific bitwidth (using the quantize() function), then I also want to make sure that all of the intermediate operations are done using another bitwidth.
Right now I am using the following equation to perform linear interpolation at specific points xq given the values y at points x. I have not included the quantize() commands below for clarity.
for j = 1:length(xq)
for k = 1:length(x)
if ((x(k) <= xq(j)) && (xq(j) < x(k+1)))
yq(j) = y(k) + (y(k+1) - y(k))/(x(k+1)-x(k)) * (xq(j)-x(k));
break;
end
end
end
Here is something that matches the dimensions of my input data. Also, I have to do this linear interpolation lots of times (over 200), so it needs to be very fast and comparable to interp1 in matlab. xq is a matrix of size 501x501 with x locations we wish to interpolate. x and y are both vectors of length 4096. y holds complex data.:
x = linspace(-100.3,100.5,4096);
y = (cos(rand(4096,1))+j*sin(rand(4096,1)))*1/100000;
for i = 1:501
xq(i,:) = (200).*rand(501,1)-100;
end
回答1:
Yup. What you can do is for each xq
value, we can find which interval the value fits into. We can use two bsxfun calls and take advantage of broadcasting to do this. Let's do a quick example. Let's say we had these x
and xq
values:
x = [1 1.5 1.7 2 2.5 2.6 2.8];
xq = [1.2 1.6 2.2];
When doing linear interpolation, our job is to figure out which interval each y
point belongs to for x
. Specifically, you want to find the index i
, such that a value of xq
at index j
satisfies:
xq(j) >= x(i) && xq(j) < x(i+1)
Let's do each part of the Boolean expression separately in a vectorized manner. For the first part, I would create a matrix where each column tells me which locations of x
satisfy the xq >= x(i)
inequality. You can do that by:
ind = bsxfun(@ge, xq, x(1:end-1).');
What we get is:
ind =
1 1 1
0 1 1
0 0 1
0 0 1
0 0 0
0 0 0
The first column is for the first value of xq
, or 1.2, the next column is 1.6, and the next column is 2.2. This tells us which values of x
that satisfies xq(1) > x(i)
. Remember, we only want to check up to the second-last element because of the i+1
condition. Therefore, for the first column, this means that xq(1) >= x(1)
, which is xq(1) >= 1
, and that's great. For the next column, this means that both xq(2) >= x(1)
and xq(2) >= x(2)
, which is both >=
than 1 and 1.5 and so on.
Now we need to do the other side of the expression:
ind2 = bsxfun(@lt, xq, x(2:end).')
ind2 =
1 0 0
1 1 0
1 1 0
1 1 1
1 1 1
1 1 1
This also makes sense. For the first column this means that for xq = 1
, every value of x
from the second element onwards (due to the i+1
) are greater than the first value of xq
, or equivalently xq(1) < x(i+1)
. Now, all you have to do is combine these together:
ind_final = ind & ind2
ind_final =
1 0 0
0 1 0
0 0 0
0 0 1
0 0 0
0 0 0
Therefore, this for each value of xq
, the row location tells us precisely which interval each value falls into. Therefore, for xq = 1.2
, this fits into interval 1 or [1,1.2]
. For xq = 1.6
, this fits into interval 2 or [1.5,1.7]
. For xq = 2.2
, this fits into interval 4, or [2,2.5]
. All you have to do now is find the row locations for each of these 1s:
[vals,~] = find(ind_final);
vals
now contains where you need to access into x
to do your interpolation. As such, you finally get:
yq = y(vals) + (y(vals+1) - y(vals))./(x(vals+1) - x(vals)).*(xq - x(vals));
Note the element-wise operators of ./
and .*
as we are doing this vectorized.
Caveat
What we didn't take into account is what happens when you specify values that are outside the range of the x
points. What I would do is have a couple statements that check for this. First, check to see if any xq
values are less than the first x
point. If they are, let's just set them to the first output point. Do the same thing for the other side when you have values that are greater than the last point and set the output to be the last point. Therefore, you would have to do something like:
%// Allocate output array first
yq = zeros(1, numel(xq));
%// Any xq values that are less than the first value, set this to the first
yq(xq < x(1)) = y(1);
%// Any xq values that are less than the last value, set this to the last value
yq(xq >= x(end)) = y(end);
%// Get all of the other values that are not within the above ranges
ind_vals = (xq >= x(1)) & (xq < x(end));
xq = xq(ind_vals);
%// Do our work
ind = bsxfun(@ge, xq, x(1:end-1).');
ind2 = bsxfun(@lt, xq, x(2:end).');
ind_final = ind & ind2;
[vals,~] = find(ind_final);
%// Place output values in the right spots, escaping those values outside the ranges
yq(ind_vals) = y(vals) + (y(vals+1) - y(vals))./(x(vals+1) - x(vals)).*(xq - x(vals))
The above code should successfully do the interpolation and handle the boundary conditions.
To see this working, let's define a bunch of x
and y
values, as well as xq
values where we wish to interpolate:
x = [1 1.5 1.7 2 2.5 2.6 2.8];
y = [2 3 4 5 5.5 6.6 7.7];
xq = [0.9 1 1.1 1.2 1.8 2.2 2.5 2.75 2.8 2.85];
Using the above, and running through the above code once I added in the range checking, we get:
yq =
2.0000 2.0000 2.2000 2.4000 4.3333 5.2000 5.5000 7.4250 7.7000 7.7000
You can see that any xq
values that are less than the first x
value, we just set the output to the first y
value. Any values that are larger than the last x
value, we set the output to the last y
value. Everything else is linearly interpolated.
回答2:
Why not use matlabs built in function to do this?
yq = interp1(x, y, xq);
回答3:
This should probably be a comment, but I lack the karma. For-loops doesn't equal slow in semi recent versions of Matlab. I have before seen cases where people jump through hoops in order to remove any loop, and end up with worse performance.
At any rate, a large part of your loop seems to be independent from j. Try something along the lines of this for a start. I doubt you'll see much improved performance by removing the loops, but I would love to be proved wrong :)
C = y(1:end-1) + (y(2:end) - y(1:end-1))./(x(2:end)-x(1:end-1))';
for j = 1:length(xq)
for k = 1:length(x)-1
if ((x(k) <= xq(j)) && (xq(j) < x(k+1)))
yq(j) = C(k) * (xq(j)-x(k));
break;
end
end
end
Using the example data, this runs about 25 % faster than your original code. Also, note that the ' at the end of the first line is only needed if x and y have different sizes as in the example, (1,4096) versus (4096,1). Also consider pre-allocating arrays like xq and yq.
回答4:
As I'm still not completely sure in which way you want to do the interpolation itself, here is just a little advice on how to reduce the complexity of your algorithm.
In its current form the algorithm is of complexity O(m*n)
for m=length(x)
and n=length(xq)
. It is possible to reduce this to O(m*log(m) + n*log(n))
.
In a low level language, you would first sort the vectors xq
and x
(and rearrange the corresponding values of y
), then iterate through the vector xq
. This way searching for the correct interval index k
can be done in a more efficient way than searching from k=1
to length(x)
. Instead you can start your search beginning at the last found value of k
.
MATLAB does however provide a convenient function histc
, which does this for you. It can return those k
-indices of the intervals the values xq
lie in. You still need to sort x
and y
though.
%%// Find the intervals the values lie in.
[x,I] = sort(x);
y = y(I);
[~, ks] = histc(xq, x);
%%// Do the interpolation.
for j = 1:length(xq)
k = ks(j);
yq(j) = y(k) + (y(k+1) - y(k))/(x(k+1)-x(k)) * (xq(j)-x(k));
end
You might be able to vectorize the for
-loop away, but as you didn't specify in what way you are actually doing the quantization step, I'll leave this to you. You might want to take a look at rayryeng's last computation step for this though. Using this histc
preprocessing step, it's just a matter of swapping vals
for ks
.
For the data-sizes listed in the question, this should give you a ~30x speedup.
来源:https://stackoverflow.com/questions/29306497/efficiently-perform-1d-linear-interpolation-without-for-loops