Fastest way to find nearest neighbor for a set of points

pclucas: <p>Hi,</p>
<p>I’m trying to implement the Chamfer distance discussed in this paper : <a href="https://arxiv.org/abs/1612.00603" rel="nofollow noopener">https://arxiv.org/abs/1612.00603</a>. To do so, I need to do the following : given 2 unordered sets of same size N, find the nearest neighbor for each point. The only way I can think of doing this is to build a NxN matrix containing the pairwise distance between each point, and then take the argmin. However, I’m not sure if this approach fully takes advantage of how parallelizable computing NN is. Any suggestions on how to approach this problem ?</p>
<p>Thank you,<br>
Lucas</p>

fmassa: <p>You can efficiently build a similarity matrix using a few tensor operators, which are parallelized in both CPU and GPU.<br>
Check <a href="https://discuss.pytorch.org/t/build-your-own-loss-function-in-pytorch/235/7?u=fmassa">Build your own loss function in PyTorch</a> for an implementation.</p>

pclucas: <p>Thank you Francisco for the fast reply. This looks like a valid solution</p>

fmassa: <p>Or alternatively, you could look into using <a href="https://github.com/facebookresearch/faiss">Faiss</a></p>

pclucas: <p>Hi again, after looking at the link you posted, it looks like it calculates the pairwise distance for points within the same set. Is there any way to adapt it for calculating the distance between 2 distinct sets ?</p>

bartolsthoorn: <p>If you don’t have too many vectors, you can also consider using scipy’s <code>cdist</code>: <a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html" rel="nofollow noopener">https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html</a><br>
It is exact but still quite fast, although an approximate nearest neighbours solution is of course faster.</p>

pclucas: <p>Hi,</p>
<p>Thanks for your answer. Unfortunately I’m using the distance as a loss function, so my implementation needs to be in pytorch so that I can back-propagate.</p>

fmassa: <p>Yes, it’s possible to adapt it to 2 different sets. I’d recommend looking into implementations of pdist2 in matlab, and adapt it to pytorch (there are a number of them available) (typing from the phone, sorry for the lack of references)</p>

Jordan_Campbell: <p>The previous answer can be adapted to compute the distances between two sets, as per <a href="https://discuss.pytorch.org/t/maximum-mean-discrepancy-mmd-and-radial-basis-function-rbf/1875/2?u=jordan_campbell">Maximum mean discrepancy (MMD) and radial basis function (rbf)</a> where <code>P</code> in that answer is the pairwise distances between all the elements of <code>X</code> and all the elements of <code>Y</code>. <code>K</code> and <code>L</code> are the within-class distances.</p>

Jenny_Jin: <p>I am also trying to build a loss function based on distance of two unordered set for a different application. Also trying to figure out how to  best do this in pytorch. I’d be very interested to know if you found any good solutions! Thanks.</p>

pclucas: <p>Hi, Thanks for all the suggestions. Using the code posted I was able to implement NN for 2 sets. Now that I’m trying to implement it in batch, I need to fetch the diagonal of a 3d tensor. In other words I have a tensor of shape bs x num_points x points_dim, and I would like to fetch the diagonal along the batch to get a bs x num_points tensor. Is there an efficient way to do so ?</p>
<p>Thanks</p>

pclucas: <pre><code>def pairwise_dist(x, y):
    xx, yy, zz = torch.mm(x,x.t()), torch.mm(y,y.t()), torch.mm(x, y.t())
    rx = (xx.diag().unsqueeze(0).expand_as(xx))
    ry = (yy.diag().unsqueeze(0).expand_as(yy))
    P = (rx.t() + ry - 2*zz)
    return P


def NN_loss(x, y, dim=0):
    dist = pairwise_dist(x, y)
    values, indices = dist.min(dim=dim)
    return values.mean()


def batch_pairwise_dist(a,b):
    x,y = a,b
    bs, num_points, points_dim = x.size()
    xx = torch.bmm(x, x.transpose(2,1))
    yy = torch.bmm(y, y.transpose(2,1))
    zz = torch.bmm(x, y.transpose(2,1))
    diag_ind = torch.arange(0, num_points).type(torch.cuda.LongTensor)
    rx = xx[:, diag_ind, diag_ind].unsqueeze(1).expand_as(xx)
    ry = yy[:, diag_ind, diag_ind].unsqueeze(1).expand_as(yy)
    P = (rx.transpose(2,1) + ry - 2*zz)
    return P


def batch_NN_loss(x, y, dim=1):
    assert dim != 0
    pdb.set_trace()
    dist = batch_pairwise_dist(x,y)
    values, indices = dist.min(dim=dim)
    return values.mean(dim=-1)
</code></pre>
<p>Here is how I ended up doing it. Not sure if I’m fetching the diagonal elements as fast as possible but it seems to be working.</p>

liangcheng: <p>How about this one:</p>
<pre><code class="lang-auto">def pairwise_dist(xyz1, xyz2):
    r_xyz1 = torch.sum(xyz1 * xyz1, dim=2, keepdim=True)  # (B,N,1)
    r_xyz2 = torch.sum(xyz2 * xyz2, dim=2, keepdim=True)  # (B,M,1)
    mul = torch.matmul(xyz2, xyz1.permute(0,2,1))         # (B,M,N)
    dist = r_xyz2 - 2 * mul + r_xyz1.permute(0,2,1)       # (B,M,N)
    return dist
</code></pre>
<p>I think this consumes less memory than your implementation.</p>

laurentperrinet: <p>another way to compute the nearest neighbor distance could be:</p>
<pre><code class="lang-auto">def get_distance_NN(particles, max=42):
    distance = (particles[:, 0].unsqueeze(0)-particles[:, 0].unsqueeze(1))**2
    distance += (particles[:, 1].unsqueeze(0)-particles[:, 1].unsqueeze(1))**2
    distance += max * torch.eye(N_particles, device=device) # exclude self
    distance_NN = distance[distance.argmin(dim=1), torch.arange(N_particles)].sqrt()
    return distance_NN
</code></pre>
<p>where here, we are in the 2D case and <code>particles</code> is a tensor containing as many lines as the number of points and the columns containing the vertical and horizontal coordinates respectively.</p>