GithubHelp home page GithubHelp logo

bkj / auction-lap Goto Github PK

View Code? Open in Web Editor NEW
70.0 2.0 18.0 107 KB

Auction algorithm for solving linear assignment problem (LAP)

Python 94.81% Shell 5.19%
auction-lap auction-algorithm linear-assignment-problem pytorch

auction-lap's People

Contributors

bkj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

auction-lap's Issues

`bids` does not change

I'm trying to understand the way this algorithm works, and I noticed that if i add print(bids.max()) inside the while (curr_ass == -1).any(): loop, then it doesn't ever change (it's always 0). but if i understand the algorithm, doesn't bids need to be updated and maintained on every loop?

i started looking into this while trying to port to pytorch 1.0 and also test on real data. i noticed that something was strange when the real data didn't work, and the algorithm ran differently even with the pytorch discrepancies fixed (i think).

Instructions from scratch in Docker

I started with a Docker image that had conda, cuda and pytorch preinstalled. I had to downgrade the pytorch version, so it might be faster to just start with an Anaconda-only Docker image. I also had to make one small change to the benchmark script.

$ git clone https://github.com/bkj/auction-lap.git && cd auction-lap
$ docker run --runtime=nvidia -it --rm --mount type=bind,src="$(pwd)",dst=/mnt anibali/pytorch:cuda-9.1 bash
# conda install pytorch==0.3.1 -c pytorch -y
# cd /mnt
# sudo apt update && sudo apt install -y build-essential
# pip install -r requirements.txt
# python benchmark.py --max-entry 1000 --eps 100 | tee results-1000-approx.jl
{"max_entry": 1000, "dim": 1000, "eps": 100.0, "gat_score": 997918, "src_score": 997918, "auc_gpu_score": 995778, "gat_time": 0.015303611755371094, "src_time": 0.015269994735717773, "auc_gpu_time": 0.009377479553222656}
{"max_entry": 1000, "dim": 2000, "eps": 100.0, "gat_score": 1997270, "src_score": 1997270, "auc_gpu_score": 1994676, "gat_time": 0.07352685928344727, "src_time": 0.07250046730041504, "auc_gpu_time": 0.01680469512939453}
{"max_entry": 1000, "dim": 3000, "eps": 100.0, "gat_score": 2996631, "src_score": 2996631, "auc_gpu_score": 2993748, "gat_time": 0.20714282989501953, "src_time": 0.227341890335083, "auc_gpu_time": 0.019696950912475586}
{"max_entry": 1000, "dim": 4000, "eps": 100.0, "gat_score": 3995860, "src_score": 3995860, "auc_gpu_score": 3992263, "gat_time": 0.36600685119628906, "src_time": 0.4125962257385254, "auc_gpu_time": 0.036045074462890625}
{"max_entry": 1000, "dim": 5000, "eps": 100.0, "gat_score": 4994927, "src_score": 4994927, "auc_gpu_score": 4991027, "gat_time": 0.5070977210998535, "src_time": 0.5707781314849854, "auc_gpu_time": 0.057653188705444336}
{"max_entry": 1000, "dim": 6000, "eps": 100.0, "gat_score": 5993973, "src_score": 5993973, "auc_gpu_score": 5989808, "gat_time": 0.6040315628051758, "src_time": 0.7091362476348877, "auc_gpu_time": 0.07340669631958008}
{"max_entry": 1000, "dim": 7000, "eps": 100.0, "gat_score": 6992986, "src_score": 6992986, "auc_gpu_score": 6989354, "gat_time": 0.7249176502227783, "src_time": 0.8651707172393799, "auc_gpu_time": 0.0954732894897461}
{"max_entry": 1000, "dim": 8000, "eps": 100.0, "gat_score": 7991996, "src_score": 7991996, "auc_gpu_score": 7988295, "gat_time": 0.8422048091888428, "src_time": 1.1722643375396729, "auc_gpu_time": 0.10980439186096191}
{"max_entry": 1000, "dim": 9000, "eps": 100.0, "gat_score": 8990999, "src_score": 8990999, "auc_gpu_score": 8987232, "gat_time": 1.0558438301086426, "src_time": 1.3648254871368408, "auc_gpu_time": 0.13532614707946777}
{"max_entry": 1000, "dim": 10000, "eps": 100.0, "gat_score": 9990000, "src_score": 9990000, "auc_gpu_score": 9986504, "gat_time": 1.2002484798431396, "src_time": 1.620683193206787, "auc_gpu_time": 0.1632983684539795}

If you don't downgrade pytorch, there seem to be some problems with the latest version:

# python benchmark.py --max-entry 1000 --eps 100 | tee results-1000-approx.jl
Traceback (most recent call last):
  File "benchmark.py", line 57, in <module>
    auc_cpu_score, auc_cpu_ass, _ = auction_lap(Xt_cpu, eps=None) # Score is accurate to within n * eps
  File "/mnt/auction-lap/auction_lap.py", line 54, in auction_lap
    src=bid_increments.view(-1, 1)
RuntimeError: dimension out of range (expected to be in range of [-1, 0], but got 1)

I think this bug can be fixed by changing this line to unassigned = (curr_ass == -1).nonzero().squeeze(dim=1) (i.e., adding dim=1).

There are bugs in 'auction_lap.py' but now I have fixed them

Hi Ben,

Thanks for sharing about the python implementation of the auction algorithm! I found some bugs in 'auction_lap.py' that actually make the codes output wrong results. I fixed them by myself and attached my codes. I am new to Pytorch. Maybe you can make my revised version better:)

  1. In line 67, (curr_ass.view(-1, 1) == have_bidder.view(1, -1)).sum(dim=1) actually outputs an array containing 0 and 1 instead of the indices of assigned bids. At each round, it actually makes the first two elements of curr_ass be -1. It can be fixed by adding .nonzero() to the tail: (curr_ass.view(-1, 1) == have_bidder.view(1, -1)).sum(dim=1).nonzero()

  2. The case when we only have 1 unassigned bidder at some round needs to be considered. Otherwise, we will get some dimensional errors. In my codes, I simply deal with this case separately.

I also tested my codes by solving a 2D points matching problem via auction algorithm and LP optimization using cvxpy package. They output the same results.

Here is the revised auction_lap.py:

#!/usr/bin/env python

from __future__ import division, print_function

import sys

import torch

def auction_lap(X, eps, compute_score=True):
    """
        X: n-by-n matrix w/ integer entries
        eps: "bid size" -- smaller values means higher accuracy w/ longer runtime
    """
   
    X = torch.from_numpy(X).float()
    # --
    # Init
    
    cost     = torch.zeros((1, X.shape[1]))
    curr_ass = torch.zeros(X.shape[0]).long() - 1
    bids     = torch.zeros(X.shape)
    
    # if X.is_cuda:
    #     cost, curr_ass, bids = cost.cuda(), curr_ass.cuda(), bids.cuda()
    
    
    counter = 0
    while (curr_ass == -1).any():
        counter += 1
        
        # --
        # Bidding
        
        unassigned = (curr_ass == -1).nonzero().squeeze()
        
        value = X[unassigned] - cost
        top_value, top_idx = value.topk(2, dim=1)
        
        first_idx = top_idx[:,0]
        first_value, second_value = top_value[:,0], top_value[:,1]
        
        bid_increments = first_value - second_value + eps
        
        bids_ = bids[unassigned]
        bids_.zero_()
        if unassigned.dim()==0:
            high_bids, high_bidders = bid_increments, unassigned
            cost[:,first_idx] += high_bids
            curr_ass[(curr_ass == first_idx).nonzero()] = -1
            curr_ass[high_bidders] = first_idx
        else:
            bids_.scatter_(
                dim=1,
                index=first_idx.contiguous().view(-1, 1),
                src=bid_increments.view(-1, 1)
            )
        
        
            have_bidder = (bids_ > 0).int().sum(dim=0).nonzero()
            
            high_bids, high_bidders = bids_[:,have_bidder].max(dim=0)
            
            high_bidders = unassigned[high_bidders.squeeze()]
            
            cost[:,have_bidder] += high_bids
            
            # curr_ass[(curr_ass.view(-1, 1) == have_bidder.view(1, -1)).sum(dim=1)] = -1
            ind = (curr_ass.view(-1, 1) == have_bidder.view(1, -1)).sum(dim=1).nonzero()
            curr_ass[ind] = -1
            
            curr_ass[high_bidders] = have_bidder.squeeze()
        
 
    score = None
    if compute_score:
        score = int(X.gather(dim=1, index=curr_ass.view(-1, 1)).sum())
    
    return score, curr_ass.numpy(), counter

Here is my testing code:

import timeit

import cvxpy as cp
import numpy as np
from scipy.sparse import lil_matrix


from auction_lap import auction_lap


np.random.seed(0)
n = 20
P = np.random.rand(n,2)
Q = np.random.rand(n,2)

start = timeit.default_timer()
C = lil_matrix((n,n))
for k in range(n):
    C[k,:] = -np.linalg.norm(P[k,:] - Q,axis=1)**2
    
C = C.toarray()
start = timeit.default_timer()
_, curr_ass, counter = auction_lap(C, eps=1e-6, compute_score=False)
stop = timeit.default_timer()
print('Autction time: ',stop-start)
M = np.array([np.linspace(0,n-1,n).astype(int).tolist(),curr_ass.tolist()])
S_auc = lil_matrix((n,n))
S_auc[M[1,:],M[0,:]] = 1
S_auc = S_auc.toarray()

start = timeit.default_timer()

S = cp.Variable((n,n))
constraints = [S >=0 , S @ np.ones(n) == 1, S.T @ np.ones(n) == 1]
prob = cp.Problem(
            cp.Maximize(cp.trace(Q.T @ S @ P)), 
            constraints
        )
prob.solve()

stop = timeit.default_timer() 
print('LP time: ',stop-start)
   
sol = S.value
sol[sol<1e-3] = 0
sol[sol>0.999] = 1

print('Difference between S_auc and sol: ',np.sum(np.abs(S_auc-sol)))

stop = timeit.default_timer()

Best,
Fengyu

Comparison with scipy

Hi.
Output of your algorithm doesn't fully agree with the scipy implementation.
Is this how it supposed to be?

Here is a code to reproduce the problem:

import torch
import numpy as np
from scipy.optimize import linear_sum_assignment
from auction_lap import auction_lap

d = 100
X = np.random.choice(100, (d, d))
X_gpu = torch.from_numpy(X.copy()).float().cuda()

_, y, _ = auction_lap(X_gpu, eps=None)
_, col_ind = linear_sum_assignment(-X)

print((y.cpu().numpy() == col_ind).mean())
# output here is only ~0.65

Also, I believe here is an error:
https://github.com/bkj/auction-lap/blob/master/auction_lap.py#L39
You need to use .squeeze(1) there.

Note that I am using pytorch 1.0.

Pytorch version upgrade

Hi,

Thanks for the great library.

Are you planning to upgrade the pytorch version you are using (0.3.1), since the code is not compatible with newer version of pytorch?

Thank you in advance!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.