Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for precomputed kernel. #73

Merged
merged 14 commits into from
Jun 16, 2021

Conversation

barucden
Copy link
Member

@barucden barucden commented Jun 9, 2021

This is an effort to properly support precomputed kernel.

There are few more steps left:

  • adjust svmpredict
  • add more tests
  • check if input sparse matrices work
  • polish the code
  • add docs

Closes #71

@barucden barucden marked this pull request as draft June 9, 2021 16:08
@codecov-commenter
Copy link

codecov-commenter commented Jun 9, 2021

Codecov Report

Merging #73 (852b2c6) into master (56e9a2e) will increase coverage by 1.86%.
The diff coverage is 98.46%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #73      +/-   ##
==========================================
+ Coverage   84.75%   86.61%   +1.86%     
==========================================
  Files           5        6       +1     
  Lines         223      254      +31     
==========================================
+ Hits          189      220      +31     
  Misses         34       34              
Impacted Files Coverage Δ
src/LIBSVM.jl 93.24% <94.11%> (-0.70%) ⬇️
src/nodes.jl 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 56e9a2e...852b2c6. Read the comment docs.

src/LIBSVM.jl Outdated Show resolved Hide resolved
src/LIBSVM.jl Outdated Show resolved Hide resolved
@iblislin
Copy link
Member

iblislin commented Jun 9, 2021

Are there any sample code of precomputed kernel in C that can help us for verifying these changes?

@barucden
Copy link
Member Author

Are there any sample code of precomputed kernel in C that can help us for verifying these changes?

There are some tests in scikit learn. The test that I have added is loosely based on it. When I passed the test-case problem to svm-train (I mean the CLI program from LIBSVM) directly, I received different results than those in scikit. In scikit, the indices of the support vectors are [1, 3], while I saw [2, 4]. I believe this is because LIBSVM uses one-based indexing, and scikit transforms that to zero-based (did not verify though). Next, coefs are differently ordered in scikit ([[-0.25, .25]] vs our [0.25; -0.25]).

Anyway, I think we can use svm-train to produce expected results for our test cases.

@barucden
Copy link
Member Author

Prediction now works (according to the simple test case, at least).

@barucden barucden force-pushed the precomputed-kernel branch 3 times, most recently from e105f0d to d0ddc4c Compare June 11, 2021 09:02
@barucden
Copy link
Member Author

I have added two more test cases. I think we can consider it working.

src/LIBSVM.jl Outdated Show resolved Hide resolved
This commit is the necessary minimum to support precomputed kernel. I
tried to keep the number of changes low, so the code could probably use
some polishing. I also added a basic test (the expected values are based
on the output of `svm-train` from LIBSVM).
We now throw `DimensionMismatch` instead of `ArgumentError` in case of
invalid shape of the input matrix.
The prediction with precomputed kernel now works. Although, the code
gets uglier and uglier.
It also fixes getting the number of instances for prediction in the case
of precomputed kernel.
@barucden barucden force-pushed the precomputed-kernel branch 2 times, most recently from 3a92dbf to a9fb62e Compare June 14, 2021 17:01
In the case of precomputed kernel, the first column of nodes must
contain indices of the instances. This special method, `gram2nodes`,
adds the indices implicitly so we don't have to add them to the input
matrix explicitly.
A method is introduced to create `SVMNode`s from ordinary instances or
from a precomputed kernel matrix. So far, it only works for full
matrices, not sparse ones.
This commit creates a new file `nodes.jl` which contains the code that
converts input matrices into `SVMNode`s.

Moreover, the function that converts instances represented as a sparse
matrix is a bit changed to use only exported functions from
SparseArrays.

When the matrix containing kernel values is sparse, we convert it to a
full matrix and use the full matrix implementation. Hopefully, it should
not matter too much as there is a good chance that sparse matrices are
not that useful for kernel values.
@barucden barucden marked this pull request as ready for review June 15, 2021 12:27
@barucden
Copy link
Member Author

Ok, I guess this is ready for reviews now.

One thing that is not so great is that sparse matrices will be converted to full matrices in the case of precomputed kernel. The reason why I did this is that I could not figure out how to reuse the code that we use for sparse matrices in the case of non-precomputed kernels. I don't think this is a significant issue since kernels tend to produce non-zero values anyway.

Copy link

@mrussek mrussek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, but obviously would like another maintainer to review

src/nodes.jl Outdated Show resolved Hide resolved
src/nodes.jl Show resolved Hide resolved
This commit introduces a constant instance of `SVMNode(-1, NaN)` that is
used as every terminal `SVMNode`.
@@ -334,6 +309,7 @@ function svmtrain(
weights = Float64[]
reverse_labels = Bool[]
else
check_train_input(X, y, kernel)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think EpsilonSVR and NuSVR also need this check?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I think so too.

The check was originally in the indices_and_weights so it's been missing for EpsilonSVR and NuSVR for quite some time. Should I add it?

Copy link
Member

@iblislin iblislin Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, adding that check in this PR is fine for me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@iblislin iblislin merged commit 5083799 into JuliaML:master Jun 16, 2021
@iblislin
Copy link
Member

@barucden I'm going to make a new release for this changes. Are there any planing PRs desired to be shipped with the new release?

@ablaom
Copy link
Contributor

ablaom commented Jun 17, 2021

@barucden Thank you indeed for this work. I realize it was more than you bargained for.

While it's fresh in your mind, could you add some brief clue in the readme documentation on the new option to specify the kernel? That will expedite updating the MLJ interface.

@barucden
Copy link
Member Author

@barucden I'm going to make a new release for this changes. Are there any planing PRs desired to be shipped with the new release?

We could address #61 in this release if you are interested. I still have the code.

@barucden Thank you indeed for this work. I realize it was more than you bargained for.

While it's fresh in your mind, could you add some brief clue in the readme documentation on the new option to specify the kernel? That will expedite updating the MLJ interface.

I added a few notes to the docs of the relevant methods (svmtrain, svmpredict) already, but I agree that a small example that shows the whole thing might help too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

How does one specify a pre-computed kernel?
5 participants