Vectorized implementation of field-aware factorization
I would like to implement the field-aware factorization model (FFM) in a vectorized way. In FFM, a prediction is made by the following equation



where w are the embeddings that depend on the feature and the field of the other feature. For more info, see equation (4) in FFM.

To do so, I have defined the following parameter:

import torch

W = torch.nn.Parameter(torch.Tensor(n_features, n_fields, n_factors), requires_grad=True)


Now, given an input x of size (batch_size, n_features), I want to be able to compute the previous equation. Here is my current (non-vectorized) implementation:

total_inter = torch.zeros(x.shape[0])
for i in range(n_features):
    for j in range(i + 1, n_features):
        temp1 = torch.mm(
            x[:, i].unsqueeze(1),
            W[i, feature2field[j], :].unsqueeze(0))
        temp2 = torch.mm(
            x[:, j].unsqueeze(1),
            W[j, feature2field[i], :].unsqueeze(0))
        total_inter += torch.sum(temp1 * temp2, dim=1)


Unsurprisingly, this implementation is horribly slow since n_features can easily be as large as 1000! Note however that most of the entries of x are 0. All inputs are appreciated!

Edit:

If it can help in any ways, here are some implementations of this model in PyTorch:


pytorch-fm
ctr_model_zoo


Unfortunately, I cannot figure out exactly how they have done it.

Additional update:

I can now obtain the product of x and W in a more efficient way by doing:

temp = torch.einsum('ij, jkl -&gt; ijkl', x, W)


Thus, my loop is now:

total_inter = torch.zeros(x.shape[0])
for i in range(n_features):
    for j in range(i + 1, n_features):
        temp1 = temp[:, i, feature2field[j], :]
        temp2 = temp[:, j, feature2field[i], :]
        total_inter += 0.5 * torch.sum(temp1 * temp2, dim=1)


It is however still too long since this loop goes over for about 500 000 iterations.


Something that could potentially help you speed up the multiplication is using pytorch sparse tensors.
Also something that might work would be the following:
Create n arrays, one for each feature i that would hold its corresponding field factors in each row. e.g. for feature i = 0


[ W[0, feature2field[0], :],
  W[0, feature2field[1], :],
  W[0, feature2field[n], :]]


Then calculate the multiplication of those arrays, lets call them F, with X

R[i] = F[i] * X


So each element in R would hold the result of the multiplication, an array, of the F[i] with X.

Next you would multiply each R[i] with its transpose

R[i] = R[i] * R[i].T


Now you can do the summation in a loop like before

for i in range(n_features):
    total_inter += torch.sum(R[i], dim=1)


Please take this with a grain of salt as i haven't tested it. In any case i think that it will point you in the right direction. 

One problem that might occur is in the transpose multiplication in which each element will also be multiplied with itself and then be added in the sum. I don't think it will affect the classifier but in any case you can make the elements in the diagonal of the transpose and above 0 (including the diagonal).

Also although minor nevertheless please move the 1st unsqueeze operation outside of the nested for loop.

I hope it helps.
