Vectorizing conditional summing loop in Matlab - matlab

I have two M by N matrices, labeled A and B, and I would like to create a vector containing the sum of all items in B for each unique value of A. For example I have the following matrices:
A = [6 2 3 4
5 2 3 3
5 5 6 2];
B = [.2 .5 .4 .1
.7 .2 .5 .1
.6 .6 .1 .9];
and I would like to create a vector C where each index corresponds to the index of D = unique(A). So for this case, D = [2,3,4,5,6] and
for I = 1:length(D)
C(I) = sum(B(A(:)==D(I));
end
This gets really slow when D is 2000 items long and A and B both are ~4000x20 matrices. Any help on speeding this up? I tried doing the following:
Indxs = bsxfun(#eq,A,reshape(D,1,1,length(D)));
for I = 1:size(Indxs,3)
C(I) = sum(B(Indxs(:,:,I));
end
but it's not really any faster.

You can do this with the usual combination of unique and accumarray:
[D, ~, uA] = unique(A(:));
C = accumarray(uA, B(:));
With your example data, the result is:
>> D
D =
2
3
4
5
6
>> C
C =
1.6000
1.0000
0.1000
1.9000
0.3000
What accumarray does, in its most basic form (two input arguments), is to sum all second-argument values that have the same first-argument value.
If A contains only positive integers, there's a one-line solution with sparse and find:
[D, ~, C] = find(sparse(A(:), 1, B(:)));
This works because sparse accumulates values corresponding to the same index.

Related

extract 3D matrix's columns based on "surface" values - vectorization

I have an NxMxK matrix A
A = [1 2 1; 1 1 2];
A = cat(3, A, [3 3 3; 3 3 3])
A(:,:,1) =
1 2 1
1 1 2
A(:,:,2) =
3 3 3
3 3 3
and I want to create a YxK 2D matrix B where K is the number of elements of A(:,:,1)==2:
k=0;
for ii=1:size(A,1)
for jj=1:size(A,2)
if A(ii,jj)==2
k=k+1;
B(k,:) = A(ii,jj,:);
end
end
end
Is there a way of vectorizing this code?
My attempt was to find the indices of A(:,:,1)==2 and then try to select the whole column but I do not know how to do it:
inds = find(A(:,:,1)==2)
B = A(inds,:) %this line does not make sense
EDIT
Preallocating B helps:
inds=find(A(:,:,1)==2);
B=NaN(numel(inds),size(A,3));
k=0;
for ii=1:size(A,1)
for jj=1:size(A,2)
if A(ii,jj)==2
k=k+1;
B(k,:) = squeeze(A(ii,jj,:));
end
end
end
But still not vectorized.
You can reshape matrix A to a (N*M)xK 2D matrix.
A = [1 2 1; 1 1 2];
A = cat(3, A, [3 3 3; 3 3 3]);
A_ = reshape(A,numel(A(:,:,1)),size(A,3));
B = A_(A_(:,1)==2,:);
Your first attempt at vectorization is almost right. Just don't use find, but use the logical matrix for indexing.
inds = A(:,:,1)==2;
The inds matrix is 2D, not 3D, so we use repmat to repeat its values along the 3rd dimension:
K = size(A,3);
inds = repmat(inds,1,1,K); % or simply cat(3,inds,inds) if K==2
B = A(inds);
The result is a column vector of size Y*K, not a matrix of size YxK, we can use reshape to fix that:
B = reshape(B,[],K);
I guess this answer is similar to Anthony's, except the indexing and the reshaping are reversed. I didn't really notice the similarity until after I wrote it down. I guess also Anthony's is a little shorter. :/

Fast multipliction of multiple matrices by multiple vectors

In matlab, I would like to multiply M vectors using L matrices, resulting with M x L new vectors. Specifically, say I have a matrix A of size N x M and a matrix B of size N x N x L matrix, I would like to calculate a matrix C of size N x M x L where the result is exactly like the following slow code:
for m=1:M
for l=1:L
C(:,m,l)=B(:,:,l)*A(:,m)
end
end
but do achieve this efficiently (using native code rather than matlab looping).
We could ab-use fast matrix-multiplication here, just need to rearrange dimensions. So, push back the second dimension of B to the end and reshape to 2D such that the first two dims are merged. Perform matrix-multiplication with A to give us a 2D array. Let's call it C. Now, C's first dim were the merged dims from B. So, split it back into their original two dim lengths with reshaping resulting in a 3D array. Finally push back the second dim to the back with one more permute. This is the desired 3D output.
Hence, the implementation would be -
permute(reshape(reshape(permute(B,[1,3,2]),[],N)*A,N,L,[]),[1,3,2])
Benchmarking
Benchmarking code :
% Setup inputs
M = 150;
L = 150;
N = 150;
A = randn(N,M);
B = randn(N,N,L);
disp('----------------------- ORIGINAL LOOPY -------------------')
tic
C_loop = NaN(N,M,L);
for m=1:M
for l=1:L
C_loop(:,m,l)=B(:,:,l)*A(:,m);
end
end
toc
disp('----------------------- BSXFUN + PERMUTE -----------------')
% #Luis's soln
tic
C = permute(sum(bsxfun(#times, permute(B, [1 2 4 3]), ...
permute(A, [3 1 2])), 2), [1 3 4 2]);
toc
disp('----------------------- BSXFUN + MATRIX-MULT -------------')
% Propose in this post
tic
out = permute(reshape(reshape(permute(B,[1,3,2]),[],N)*A,N,L,[]),[1,3,2]);
toc
Timings :
----------------------- ORIGINAL LOOPY -------------------
Elapsed time is 0.905811 seconds.
----------------------- BSXFUN + PERMUTE -----------------
Elapsed time is 0.883616 seconds.
----------------------- BSXFUN + MATRIX-MULT -------------
Elapsed time is 0.045331 seconds.
You can do it with some permuting of dimensions and singleton expansion:
C = permute(sum(bsxfun(#times, permute(B, [1 2 4 3]), permute(A, [3 1 2])), 2), [1 3 4 2]);
Check:
% Example inputs:
M = 5;
L = 6;
N = 7;
A = randn(N,M);
B = randn(N,N,L);
% Output with bsxfun and permute:
C = permute(sum(bsxfun(#times, permute(B, [1 2 4 3]), permute(A, [3 1 2])), 2), [1 3 4 2]);
% Output with loops:
C_loop = NaN(N,M,L);
for m=1:M
for l=1:L
C_loop(:,m,l)=B(:,:,l)*A(:,m);
end
end
% Maximum relative error. Should be 0, or of the order of eps:
max_error = max(reshape(abs(C./C_loop),[],1)-1)

Multiply each column in a matrix by corresponding row in another and sum results in Matlab

Lets say I have matrices A = [1 2; 3 4], B = [4 3; 2 1]. I want to multiply each column from matrix A ([1; 3], [2; 4]) by the corresponding row in matrix B ([4 3], [2 1]) and sum resulting matrices. I have came up with the following code:
C = zeros(size(A));
for i = 1 : size(A, 1)
C = C + A(:, i) * B(i, :);
end
Could it be rewritten using some math trick or matlab function to get rid of the for loop?
I see there is unclarity in my question regarding the result I want. The result should exactly mimic provided Matlab code, therefore I seek one matrix which is given by the matrix summation of the intermediate matrices that are created by multiplying each column vector with corresponding row vector from both matrices. For this specific example, it would be given by
C = A(:, 1) * B(1, :) + A(:, 2) * B(2, :);
I am just looking for some generic, for-loop less version for any matrices of compatible dimensions.
I just tried out my suggestion in the comments, and it seems to work with this octave tester:
Short form (only works in Octave):
A = [1 2; 3 4], B = [4 3; 2 1]
X = sum((A * B)(:))
Long form (Matlab):
A = [1 2; 3 4]
B = [4 3; 2 1]
C = A * B % Stop here if you want the exact result from your Matlab code
x = sum(C(:)) % To get the sum of the resulting matrix
Sources:
https://www.tutorialspoint.com/matlab/matlab_matrix_multiplication.htm
https://www.mathworks.com/matlabcentral/newsreader/view_thread/51252
Update, based on your update:
Output of A * B:
8 5
20 13
Output of your code:
8 5
20 13
It appears that
C = zeros(size(A));
for i = 1 : size(A, 1)
C = C + A(:, i) * B(i, :);
end
is equivalent to the matrix multiplication
C = A*B
for square matrices A and B.
You can also do this in MATLAB, to get the sum.
C=ones(1,2)*A*B*ones(2,1)
The general form would be
C=ones(1,size(A,1))*(A*B)*ones(size(B,2),1);
EDIT
I see you updated your question for clarity. The matrix product can be calculated directly
C = A*B;
as pointed out by jodag.
This works provided you follow rules of linear algebra where inner dimensions of your matrices are the same (i.e. when number of columns in A match the number of rows in B; size(A,2)==size(B,1)).

Computing Camera Matrix Using MATLAB

I'm currently trying to compute the camera matrix P given a set of world points (X) with its corresponding image points (x). However, when testing for the the result, P (3 x 4 camera matrix) multiplying by the world points does not give me the correct corresponding image points. However, only the first column of PX = x. The other column won't return the approximate image points.
Code:
X = [1 2 3; 4 5 6; 7 8 9; 1 1 1];
x = [3 2 1; 6 5 4; 1 1 1];
[mX, nX] = size(X);
[mx, nx] = size(x);
for i = 0:(nX-1)
XX{i+1} = transpose(X(1+i: 4+i));
end
for i = 0:(nx-1)
xx{i+1} = transpose(x(i+1:3+i));
end
%TODO - normalization
A = [];
%construct matrix
for i = 1:nX
A = [A; zeros(1,4) -1*(xx{i}(3)*transpose(XX{i})) xx{i}(2)*transpose(XX{i})];
A = [A; xx{i}(3)*transpose(XX{i}) zeros(1,4) -1*xx{i}(1)*transpose(XX{i})];
end
%using svd to solve for non zero solution
[u s v] = svd(A);
p = v(:, size(v,2));
p = reshape(p, 4,3)';
output for the first column, works good:
>> p*XX{1}
ans =
0.0461
0.0922
0.0154
>> ans/0.0154
ans =
2.9921
5.9841
0.9974
>> xx{1}
ans =
3
6
1
output for the second column, doesn't work:
>> p*XX{2}
ans =
0.5202
0.0867
0.1734
>> ans/0.1734
ans =
2.9999
0.5000
1.0000
>> xx{2}
ans =
6
1
2
By the way, I was told that I need to normalize the world points and image points before I compute the camera matrix. I have not done this step and have no idea how to. If this is causing the issue, please explain what can be done. Thank you in advance.
This is because you aren't indexing into the matrix properly. You are using linear indexing to access each column of the matrix. In that case, your for loop needs to access each column independently. Therefore, each iteration of your for loop must access groups of 4 elements for your 3D points and groups of 3 elements for your 2D points.
As such, you simply need to do this for your for loops:
for i = 0:(nX-1)
XX{i+1} = transpose(X(4*i + 1 : 4*(i + 1)));
end
for i = 0:(nx-1)
xx{i+1} = transpose(x(3*i + 1 : 3*(i + 1)));
end
After this, the code should work no problem. To verify, we can loop through each 3D point and determine its 2D equivalent as you're using cells:
out = zeros(size(xx)); % Declare output matrix
for ii = 1 : numel(XX) % For each 3D point...
out(:,ii) = p * XX{ii}; % Transform the point
out(:,ii) = out(:,ii) / out(end,ii); % Normalize
end
We thus get:
>> out
out =
3.0000 2.0000 1.0000
6.0000 5.0000 4.0000
1.0000 1.0000 1.0000
Compare with your x:
>> x
x =
3 2 1
6 5 4
1 1 1
Suggestion - Use vectorization
If I can suggest something, please do not use cell arrays here. You can create the matrix of equations for solving using vectorization. Specifically, you can create the matrix A directly without any for loops:
A = [zeros(N, 4) -X.' bsxfun(#times, x(2,:).', X.');
X.' zeros(N, 4) bsxfun(#times, -x(1,:).', X.')];
If you own MATLAB R2016b and up, you can do this with internal broadcasting:
A = [zeros(N, 4) -X.' x(2,:).' .* X.';
X.' zeros(N, 4) -x(1,:).' .* X.']
Note that you will see the rows are shuffled in comparison to your original matrix A because of the vectorization. Because we are solving for the null space of the matrix A, shuffling the rows has no effect. Therefore, your code can be simplified to:
X = [1 2 3; 4 5 6; 7 8 9; 1 1 1];
x = [3 2 1; 6 5 4; 1 1 1];
A = [zeros(N, 4) -X.' bsxfun(#times, x(2,:).', X.');
X.' zeros(N, 4) bsxfun(#times, -x(1,:).', X.')];
% Use this for MATLAB R2016b and up
% A = [zeros(N, 4) -X.' x(2,:).' .* X.';
% X.' zeros(N, 4) -x(1,:).' .* X.']
[u, s, v] = svd(A);
p = v(:, end);
p = reshape(p, 4, 3).';
To finally compute the output matrix, you can just use simple matrix multiplication. The fact that you are using cells requires that you have to use a for loop and it's much faster to do this with matrix multiplication:
out = p * X;
You can then take the last row of the results and divide each of the other rows by this row.
out = bsxfun(#rdivide, out, out(end,:));
Again with MATLAB R2016b and up, you can just do it as so:
out = out ./ out(end,:);

outer product of matrix rows (e.g. A(i,:)^T * A(i,:) for all rows i) + mean of same element in all submatrices created

I'm trying to calculate the outer product of all rows in a matrix. So far I'm doing the following:
A = rand(10,8);
[N J] = size(A);
for i = 1:N,
B(((i-1)*J+1):J+((i-1)*J),:) = A(i,:)'*A(i,:)
end
I then take the mean of the same elements of the same row in each submatrix created above.
for j=1:J,
C(1:J,j) = accumarray(repmat((1:J)',N,1),B(:,j)).*(1/N);
end
Now this works, but my input matrix will eventually be a number of magnitudes larger, so a vectorized version would be nice. I assume there is some way to do this with permute and bsxfun but the solutions for outer product I have seen so far don't seem to apply here.
Sure. You can compute C directly as
C = mean(bsxfun(#times, permute(A, [2 3 1]), permute(A, [3 2 1])), 3);
Or, if you really need the B variable, you can do it this way:
B_3D = bsxfun(#times, A.', permute(A,[3 1 2])); %'// 3D array
B = reshape(B_3D, numel(A), []);
C = mean(permute(B_3D, [1 3 2]), 3);