bkj / auction-lap Goto Github PK
View Code? Open in Web Editor NEWAuction algorithm for solving linear assignment problem (LAP)
Auction algorithm for solving linear assignment problem (LAP)
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).
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
).
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:)
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()
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
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.
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!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.