rumale-kernel_machine 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +27 -0
- data/README.md +34 -0
- data/lib/rumale/kernel_machine/kernel_fda.rb +121 -0
- data/lib/rumale/kernel_machine/kernel_pca.rb +98 -0
- data/lib/rumale/kernel_machine/kernel_ridge.rb +81 -0
- data/lib/rumale/kernel_machine/kernel_ridge_classifier.rb +94 -0
- data/lib/rumale/kernel_machine/kernel_svc.rb +187 -0
- data/lib/rumale/kernel_machine/version.rb +10 -0
- data/lib/rumale/kernel_machine.rb +10 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6c53bda994de48371ca60e6fb8e957a0f77e96943de0f73d989deeaa9edbcf89
|
4
|
+
data.tar.gz: 6a08ecbfd73f1e9b64a29ffd11114766bebc5b9b8cf8def6325e89528af50b82
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7a1b192c7e8c31f769d2fef8ccf8be830bf0e129e0249269e807cd9e69f7bed06c8968612515b4d7817de59be4755e713dbf1a0e206b9dc4f6cd55308cf0111e
|
7
|
+
data.tar.gz: cd368663f5f48eca166ac8b3832df2c26e24ac09e64164886bf3815ba308f0cb64fd908768765d126d62d5103ec1edc99adf8a54f1900ee6581bbe5e17a65cb3
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2022 Atsushi Tatsuma
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* Neither the name of the copyright holder nor the names of its
|
15
|
+
contributors may be used to endorse or promote products derived from
|
16
|
+
this software without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Rumale::KernelMachine
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/rumale-kernel_machine)
|
4
|
+
[](https://github.com/yoshoku/rumale/blob/main/rumale-kernel_machine/LICENSE.txt)
|
5
|
+
[](https://yoshoku.github.io/rumale/doc/Rumale/KernelMachine.html)
|
6
|
+
|
7
|
+
Rumale is a machine learning library in Ruby.
|
8
|
+
Rumale::KernelMachine provides kernel method-based algorithms,
|
9
|
+
such as Kernel Support Vector Machine, Kernel Principal Componenet Analysis, and Kernel Ridge Regression,
|
10
|
+
with Rumale interface.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'rumale-kernel_machine'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle install
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install rumale-kernel_machine
|
27
|
+
|
28
|
+
## Documentation
|
29
|
+
|
30
|
+
- [Rumale API Documentation - KernelMachine](https://yoshoku.github.io/rumale/doc/Rumale/KernelMachine.html)
|
31
|
+
|
32
|
+
## License
|
33
|
+
|
34
|
+
The gem is available as open source under the terms of the [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause).
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rumale/base/estimator'
|
4
|
+
require 'rumale/base/transformer'
|
5
|
+
require 'rumale/validation'
|
6
|
+
|
7
|
+
module Rumale
|
8
|
+
module KernelMachine
|
9
|
+
# KernelFDA is a class that implements Kernel Fisher Discriminant Analysis.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# require 'numo/linalg/autoloader'
|
13
|
+
# require 'rumale/pairwise_metric'
|
14
|
+
# require 'rumale/kernel_machine/kernel_fda'
|
15
|
+
#
|
16
|
+
# kernel_mat_train = Rumale::PairwiseMetric::rbf_kernel(x_train)
|
17
|
+
# kfda = Rumale::KernelMachine::KernelFDA.new
|
18
|
+
# mapped_traininig_samples = kfda.fit_transform(kernel_mat_train, y)
|
19
|
+
#
|
20
|
+
# kernel_mat_test = Rumale::PairwiseMetric::rbf_kernel(x_test, x_train)
|
21
|
+
# mapped_test_samples = kfda.transform(kernel_mat_test)
|
22
|
+
#
|
23
|
+
# *Reference*
|
24
|
+
# - Baudat, G., and Anouar, F., "Generalized Discriminant Analysis using a Kernel Approach," Neural Computation, vol. 12, pp. 2385--2404, 2000.
|
25
|
+
class KernelFDA < ::Rumale::Base::Estimator
|
26
|
+
include ::Rumale::Base::Transformer
|
27
|
+
|
28
|
+
# Returns the eigenvectors for embedding.
|
29
|
+
# @return [Numo::DFloat] (shape: [n_training_sampes, n_components])
|
30
|
+
attr_reader :alphas
|
31
|
+
|
32
|
+
# Create a new transformer with Kernel FDA.
|
33
|
+
#
|
34
|
+
# @param n_components [Integer] The number of components.
|
35
|
+
# @param reg_param [Float] The regularization parameter.
|
36
|
+
def initialize(n_components: nil, reg_param: 1e-8)
|
37
|
+
super()
|
38
|
+
@params = {
|
39
|
+
n_components: n_components,
|
40
|
+
reg_param: reg_param
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Fit the model with given training data.
|
45
|
+
# To execute this method, Numo::Linalg must be loaded.
|
46
|
+
#
|
47
|
+
# @param x [Numo::DFloat] (shape: [n_training_samples, n_training_samples])
|
48
|
+
# The kernel matrix of the training data to be used for fitting the model.
|
49
|
+
# @param y [Numo::Int32] (shape: [n_samples]) The labels to be used for fitting the model.
|
50
|
+
# @return [KernelFDA] The learned transformer itself.
|
51
|
+
def fit(x, y)
|
52
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
53
|
+
y = ::Rumale::Validation.check_convert_label_array(y)
|
54
|
+
::Rumale::Validation.check_sample_size(x, y)
|
55
|
+
raise ArgumentError, 'Expect the kernel matrix of training data to be square.' unless x.shape[0] == x.shape[1]
|
56
|
+
raise 'KernelFDA#fit requires Numo::Linalg but that is not loaded.' unless enable_linalg?(warning: false)
|
57
|
+
|
58
|
+
# initialize some variables.
|
59
|
+
n_samples = x.shape[0]
|
60
|
+
@classes = Numo::Int32[*y.to_a.uniq.sort]
|
61
|
+
n_classes = @classes.size
|
62
|
+
n_components = if @params[:n_components].nil?
|
63
|
+
[n_samples, n_classes - 1].min
|
64
|
+
else
|
65
|
+
[n_samples, @params[:n_components]].min
|
66
|
+
end
|
67
|
+
|
68
|
+
# centering
|
69
|
+
@row_mean = x.mean(0)
|
70
|
+
@all_mean = @row_mean.sum.fdiv(n_samples)
|
71
|
+
centered_kernel_mat = x - x.mean(1).expand_dims(1) - @row_mean + @all_mean
|
72
|
+
|
73
|
+
# calculate between and within scatter matrix.
|
74
|
+
class_mat = Numo::DFloat.zeros(n_samples, n_samples)
|
75
|
+
@classes.each do |label|
|
76
|
+
idx_vec = y.eq(label)
|
77
|
+
class_mat += Numo::DFloat.cast(idx_vec).outer(idx_vec) / idx_vec.count
|
78
|
+
end
|
79
|
+
between_mat = centered_kernel_mat.dot(class_mat).dot(centered_kernel_mat.transpose)
|
80
|
+
within_mat = centered_kernel_mat.dot(centered_kernel_mat.transpose) + @params[:reg_param] * Numo::DFloat.eye(n_samples)
|
81
|
+
|
82
|
+
# calculate projection matrix.
|
83
|
+
_, eig_vecs = Numo::Linalg.eigh(
|
84
|
+
between_mat, within_mat,
|
85
|
+
vals_range: (n_samples - n_components)...n_samples
|
86
|
+
)
|
87
|
+
@alphas = eig_vecs.reverse(1).dup
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# Fit the model with training data, and then transform them with the learned model.
|
92
|
+
# To execute this method, Numo::Linalg must be loaded.
|
93
|
+
#
|
94
|
+
# @param x [Numo::DFloat] (shape: [n_samples, n_samples])
|
95
|
+
# The kernel matrix of the training data to be used for fitting the model and transformed.
|
96
|
+
# @param y [Numo::Int32] (shape: [n_samples]) The labels to be used for fitting the model.
|
97
|
+
# @return [Numo::DFloat] (shape: [n_samples, n_components]) The transformed data
|
98
|
+
def fit_transform(x, y)
|
99
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
100
|
+
y = ::Rumale::Validation.check_convert_label_array(y)
|
101
|
+
::Rumale::Validation.check_sample_size(x, y)
|
102
|
+
|
103
|
+
fit(x, y).transform(x)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Transform the given data with the learned model.
|
107
|
+
#
|
108
|
+
# @param x [Numo::DFloat] (shape: [n_testing_samples, n_training_samples])
|
109
|
+
# The kernel matrix between testing samples and training samples to be transformed.
|
110
|
+
# @return [Numo::DFloat] (shape: [n_testing_samples, n_components]) The transformed data.
|
111
|
+
def transform(x)
|
112
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
113
|
+
|
114
|
+
col_mean = x.sum(axis: 1) / @row_mean.shape[0]
|
115
|
+
centered_kernel_mat = x - col_mean.expand_dims(1) - @row_mean + @all_mean
|
116
|
+
transformed = centered_kernel_mat.dot(@alphas)
|
117
|
+
@params[:n_components] == 1 ? transformed[true, 0].dup : transformed
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rumale/base/estimator'
|
4
|
+
require 'rumale/base/transformer'
|
5
|
+
require 'rumale/validation'
|
6
|
+
|
7
|
+
module Rumale
|
8
|
+
module KernelMachine
|
9
|
+
# KernelPCA is a class that implements Kernel Principal Component Analysis.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# require 'numo/linalg/autoloader'
|
13
|
+
# require 'rumale/pairwise_metric'
|
14
|
+
# require 'rumale/kernel_machine/kernel_pca'
|
15
|
+
#
|
16
|
+
# kernel_mat_train = Rumale::PairwiseMetric::rbf_kernel(training_samples)
|
17
|
+
# kpca = Rumale::KernelMachine::KernelPCA.new(n_components: 2)
|
18
|
+
# mapped_traininig_samples = kpca.fit_transform(kernel_mat_train)
|
19
|
+
#
|
20
|
+
# kernel_mat_test = Rumale::PairwiseMetric::rbf_kernel(test_samples, training_samples)
|
21
|
+
# mapped_test_samples = kpca.transform(kernel_mat_test)
|
22
|
+
#
|
23
|
+
# *Reference*
|
24
|
+
# - Scholkopf, B., Smola, A., and Muller, K-R., "Nonlinear Component Analysis as a Kernel Eigenvalue Problem," Neural Computation, Vol. 10 (5), pp. 1299--1319, 1998.
|
25
|
+
class KernelPCA < ::Rumale::Base::Estimator
|
26
|
+
include ::Rumale::Base::Transformer
|
27
|
+
|
28
|
+
# Returns the eigenvalues of the centered kernel matrix.
|
29
|
+
# @return [Numo::DFloat] (shape: [n_components])
|
30
|
+
attr_reader :lambdas
|
31
|
+
|
32
|
+
# Returns the eigenvectors of the centered kernel matrix.
|
33
|
+
# @return [Numo::DFloat] (shape: [n_training_sampes, n_components])
|
34
|
+
attr_reader :alphas
|
35
|
+
|
36
|
+
# Create a new transformer with Kernel PCA.
|
37
|
+
#
|
38
|
+
# @param n_components [Integer] The number of components.
|
39
|
+
def initialize(n_components: 2)
|
40
|
+
super()
|
41
|
+
@params = {
|
42
|
+
n_components: n_components
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
# Fit the model with given training data.
|
47
|
+
# To execute this method, Numo::Linalg must be loaded.
|
48
|
+
#
|
49
|
+
# @overload fit(x) -> KernelPCA
|
50
|
+
# @param x [Numo::DFloat] (shape: [n_training_samples, n_training_samples])
|
51
|
+
# The kernel matrix of the training data to be used for fitting the model.
|
52
|
+
# @return [KernelPCA] The learned transformer itself.
|
53
|
+
def fit(x, _y = nil)
|
54
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
55
|
+
raise ArgumentError, 'Expect the kernel matrix of training data to be square.' unless x.shape[0] == x.shape[1]
|
56
|
+
raise 'KernelPCA#fit requires Numo::Linalg but that is not loaded.' unless enable_linalg?(warning: false)
|
57
|
+
|
58
|
+
n_samples = x.shape[0]
|
59
|
+
@row_mean = x.mean(0)
|
60
|
+
@all_mean = @row_mean.sum.fdiv(n_samples)
|
61
|
+
centered_kernel_mat = x - x.mean(1).expand_dims(1) - @row_mean + @all_mean
|
62
|
+
eig_vals, eig_vecs = Numo::Linalg.eigh(centered_kernel_mat,
|
63
|
+
vals_range: (n_samples - @params[:n_components])...n_samples)
|
64
|
+
@alphas = eig_vecs.reverse(1).dup
|
65
|
+
@lambdas = eig_vals.reverse.dup
|
66
|
+
@transform_mat = @alphas.dot((1.0 / Numo::NMath.sqrt(@lambdas)).diag)
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Fit the model with training data, and then transform them with the learned model.
|
71
|
+
# To execute this method, Numo::Linalg must be loaded.
|
72
|
+
#
|
73
|
+
# @overload fit_transform(x) -> Numo::DFloat
|
74
|
+
# @param x [Numo::DFloat] (shape: [n_samples, n_samples])
|
75
|
+
# The kernel matrix of the training data to be used for fitting the model and transformed.
|
76
|
+
# @return [Numo::DFloat] (shape: [n_samples, n_components]) The transformed data
|
77
|
+
def fit_transform(x, _y = nil)
|
78
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
79
|
+
|
80
|
+
fit(x).transform(x)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Transform the given data with the learned model.
|
84
|
+
#
|
85
|
+
# @param x [Numo::DFloat] (shape: [n_testing_samples, n_training_samples])
|
86
|
+
# The kernel matrix between testing samples and training samples to be transformed.
|
87
|
+
# @return [Numo::DFloat] (shape: [n_testing_samples, n_components]) The transformed data.
|
88
|
+
def transform(x)
|
89
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
90
|
+
|
91
|
+
col_mean = x.sum(axis: 1) / @row_mean.shape[0]
|
92
|
+
centered_kernel_mat = x - col_mean.expand_dims(1) - @row_mean + @all_mean
|
93
|
+
transformed = centered_kernel_mat.dot(@transform_mat)
|
94
|
+
@params[:n_components] == 1 ? transformed[true, 0].dup : transformed
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rumale/base/estimator'
|
4
|
+
require 'rumale/base/regressor'
|
5
|
+
require 'rumale/validation'
|
6
|
+
|
7
|
+
module Rumale
|
8
|
+
module KernelMachine
|
9
|
+
# KernelRidge is a class that implements kernel ridge regression.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# require 'numo/linalg/autoloader'
|
13
|
+
# require 'rumale/pairwise_metric'
|
14
|
+
# require 'rumale/kernel_machine/kernel_ridge'
|
15
|
+
#
|
16
|
+
# kernel_mat_train = Rumale::PairwiseMetric::rbf_kernel(training_samples)
|
17
|
+
# kridge = Rumale::KernelMachine::KernelRidge.new(reg_param: 1.0)
|
18
|
+
# kridge.fit(kernel_mat_train, traininig_values)
|
19
|
+
#
|
20
|
+
# kernel_mat_test = Rumale::PairwiseMetric::rbf_kernel(test_samples, training_samples)
|
21
|
+
# results = kridge.predict(kernel_mat_test)
|
22
|
+
class KernelRidge < ::Rumale::Base::Estimator
|
23
|
+
include ::Rumale::Base::Regressor
|
24
|
+
|
25
|
+
# Return the weight vector.
|
26
|
+
# @return [Numo::DFloat] (shape: [n_training_sample, n_outputs])
|
27
|
+
attr_reader :weight_vec
|
28
|
+
|
29
|
+
# Create a new regressor with kernel ridge regression.
|
30
|
+
#
|
31
|
+
# @param reg_param [Float/Numo::DFloat] The regularization parameter.
|
32
|
+
def initialize(reg_param: 1.0)
|
33
|
+
super()
|
34
|
+
@params = {
|
35
|
+
reg_param: reg_param
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Fit the model with given training data.
|
40
|
+
#
|
41
|
+
# @param x [Numo::DFloat] (shape: [n_training_samples, n_training_samples])
|
42
|
+
# The kernel matrix of the training data to be used for fitting the model.
|
43
|
+
# @param y [Numo::DFloat] (shape: [n_samples, n_outputs]) The taget values to be used for fitting the model.
|
44
|
+
# @return [KernelRidge] The learned regressor itself.
|
45
|
+
def fit(x, y)
|
46
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
47
|
+
y = ::Rumale::Validation.check_convert_target_value_array(y)
|
48
|
+
::Rumale::Validation.check_sample_size(x, y)
|
49
|
+
raise ArgumentError, 'Expect the kernel matrix of training data to be square.' unless x.shape[0] == x.shape[1]
|
50
|
+
raise 'KernelRidge#fit requires Numo::Linalg but that is not loaded.' unless enable_linalg?(warning: false)
|
51
|
+
|
52
|
+
n_samples = x.shape[0]
|
53
|
+
|
54
|
+
if @params[:reg_param].is_a?(Float)
|
55
|
+
reg_kernel_mat = x + Numo::DFloat.eye(n_samples) * @params[:reg_param]
|
56
|
+
@weight_vec = Numo::Linalg.solve(reg_kernel_mat, y, driver: 'sym')
|
57
|
+
else
|
58
|
+
n_outputs = y.shape[1]
|
59
|
+
@weight_vec = Numo::DFloat.zeros(n_samples, n_outputs)
|
60
|
+
n_outputs.times do |n|
|
61
|
+
reg_kernel_mat = x + Numo::DFloat.eye(n_samples) * @params[:reg_param][n]
|
62
|
+
@weight_vec[true, n] = Numo::Linalg.solve(reg_kernel_mat, y[true, n], driver: 'sym')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Predict values for samples.
|
70
|
+
#
|
71
|
+
# @param x [Numo::DFloat] (shape: [n_testing_samples, n_training_samples])
|
72
|
+
# The kernel matrix between testing samples and training samples to predict values.
|
73
|
+
# @return [Numo::DFloat] (shape: [n_samples, n_outputs]) Predicted values per sample.
|
74
|
+
def predict(x)
|
75
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
76
|
+
|
77
|
+
x.dot(@weight_vec)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rumale/base/estimator'
|
4
|
+
require 'rumale/base/classifier'
|
5
|
+
require 'rumale/utils'
|
6
|
+
require 'rumale/validation'
|
7
|
+
|
8
|
+
module Rumale
|
9
|
+
module KernelMachine
|
10
|
+
# KernelRidgeClassifier is a class that implements classifier based-on kernel ridge regression.
|
11
|
+
# It learns a classifier by converting labels to target values { -1, 1 } and performing kernel ridge regression.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# require 'numo/linalg/autoloader'
|
15
|
+
# require 'rumale/pairwise_metric'
|
16
|
+
# require 'rumale/kernel_machine/kernel_ridge_classifier'
|
17
|
+
#
|
18
|
+
# kernel_mat_train = Rumale::PairwiseMetric::rbf_kernel(training_samples)
|
19
|
+
# kridge = Rumale::KernelMachine::KernelRidgeClassifier.new(reg_param: 0.5)
|
20
|
+
# kridge.fit(kernel_mat_train, traininig_values)
|
21
|
+
#
|
22
|
+
# kernel_mat_test = Rumale::PairwiseMetric::rbf_kernel(test_samples, training_samples)
|
23
|
+
# results = kridge.predict(kernel_mat_test)
|
24
|
+
class KernelRidgeClassifier < ::Rumale::Base::Estimator
|
25
|
+
include ::Rumale::Base::Classifier
|
26
|
+
|
27
|
+
# Return the class labels.
|
28
|
+
# @return [Numo::Int32] (size: n_classes)
|
29
|
+
attr_reader :classes
|
30
|
+
|
31
|
+
# Return the weight vector.
|
32
|
+
# @return [Numo::DFloat] (shape: [n_training_sample, n_classes])
|
33
|
+
attr_reader :weight_vec
|
34
|
+
|
35
|
+
# Create a new regressor with kernel ridge classifier.
|
36
|
+
#
|
37
|
+
# @param reg_param [Float/Numo::DFloat] The regularization parameter.
|
38
|
+
def initialize(reg_param: 1.0)
|
39
|
+
super()
|
40
|
+
@params = {
|
41
|
+
reg_param: reg_param
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Fit the model with given training data.
|
46
|
+
#
|
47
|
+
# @param x [Numo::DFloat] (shape: [n_training_samples, n_training_samples])
|
48
|
+
# The kernel matrix of the training data to be used for fitting the model.
|
49
|
+
# @param y [Numo::Int32] (shape: [n_training_samples]) The labels to be used for fitting the model.
|
50
|
+
# @return [KernelRidgeClassifier] The learned classifier itself.
|
51
|
+
def fit(x, y)
|
52
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
53
|
+
y = ::Rumale::Validation.check_convert_label_array(y)
|
54
|
+
::Rumale::Validation.check_sample_size(x, y)
|
55
|
+
raise ArgumentError, 'Expect the kernel matrix of training data to be square.' unless x.shape[0] == x.shape[1]
|
56
|
+
raise 'KernelRidgeClassifier#fit requires Numo::Linalg but that is not loaded.' unless enable_linalg?(warning: false)
|
57
|
+
|
58
|
+
y_encoded = Numo::DFloat.cast(::Rumale::Utils.binarize_labels(y)) * 2 - 1
|
59
|
+
@classes = Numo::NArray[*y.to_a.uniq.sort]
|
60
|
+
|
61
|
+
n_samples = x.shape[0]
|
62
|
+
reg_kernel_mat = x + Numo::DFloat.eye(n_samples) * @params[:reg_param]
|
63
|
+
@weight_vec = Numo::Linalg.solve(reg_kernel_mat, y_encoded, driver: 'sym')
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Calculate confidence scores for samples.
|
69
|
+
#
|
70
|
+
# @param x [Numo::DFloat] (shape: [n_testing_samples, n_training_samples])
|
71
|
+
# The kernel matrix between testing samples and training samples to predict values.
|
72
|
+
# @return [Numo::DFloat] (shape: [n_samples, n_classes]) The confidence score per sample.
|
73
|
+
def decision_function(x)
|
74
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
75
|
+
|
76
|
+
x.dot(@weight_vec)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Predict class labels for samples.
|
80
|
+
#
|
81
|
+
# @param x [Numo::DFloat] (shape: [n_testing_samples, n_training_samples])
|
82
|
+
# The kernel matrix between testing samples and training samples to predict the labels.
|
83
|
+
# @return [Numo::Int32] (shape: [n_testing_samples]) Predicted class label per sample.
|
84
|
+
def predict(x)
|
85
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
86
|
+
|
87
|
+
scores = decision_function(x)
|
88
|
+
n_samples, n_classes = scores.shape
|
89
|
+
label_ids = scores.max_index(axis: 1) - Numo::Int32.new(n_samples).seq * n_classes
|
90
|
+
@classes[label_ids].dup
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rumale/base/estimator'
|
4
|
+
require 'rumale/base/classifier'
|
5
|
+
require 'rumale/probabilistic_output'
|
6
|
+
require 'rumale/validation'
|
7
|
+
|
8
|
+
module Rumale
|
9
|
+
module KernelMachine
|
10
|
+
# KernelSVC is a class that implements (Nonlinear) Kernel Support Vector Classifier
|
11
|
+
# with stochastic gradient descent (SGD) optimization.
|
12
|
+
# For multiclass classification problem, it uses one-vs-the-rest strategy.
|
13
|
+
#
|
14
|
+
# @note
|
15
|
+
# Rumale::SVM provides kernel support vector classifier based on LIBSVM.
|
16
|
+
# If you prefer execution speed, you should use Rumale::SVM::SVC.
|
17
|
+
# https://github.com/yoshoku/rumale-svm
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# require 'rumale/pairwise_metric'
|
21
|
+
# require 'rumale/kernel_machine/kernel_svc'
|
22
|
+
#
|
23
|
+
# training_kernel_matrix = Rumale::PairwiseMetric::rbf_kernel(training_samples)
|
24
|
+
# estimator =
|
25
|
+
# Rumale::KernelMachine::KernelSVC.new(reg_param: 1.0, max_iter: 1000, random_seed: 1)
|
26
|
+
# estimator.fit(training_kernel_matrix, traininig_labels)
|
27
|
+
# testing_kernel_matrix = Rumale::PairwiseMetric::rbf_kernel(testing_samples, training_samples)
|
28
|
+
# results = estimator.predict(testing_kernel_matrix)
|
29
|
+
#
|
30
|
+
# *Reference*
|
31
|
+
# - Shalev-Shwartz, S., Singer, Y., Srebro, N., and Cotter, A., "Pegasos: Primal Estimated sub-GrAdient SOlver for SVM," Mathematical Programming, vol. 127 (1), pp. 3--30, 2011.
|
32
|
+
class KernelSVC < ::Rumale::Base::Estimator
|
33
|
+
include ::Rumale::Base::Classifier
|
34
|
+
|
35
|
+
# Return the weight vector for Kernel SVC.
|
36
|
+
# @return [Numo::DFloat] (shape: [n_classes, n_trainig_sample])
|
37
|
+
attr_reader :weight_vec
|
38
|
+
|
39
|
+
# Return the class labels.
|
40
|
+
# @return [Numo::Int32] (shape: [n_classes])
|
41
|
+
attr_reader :classes
|
42
|
+
|
43
|
+
# Return the random generator for performing random sampling.
|
44
|
+
# @return [Random]
|
45
|
+
attr_reader :rng
|
46
|
+
|
47
|
+
# Create a new classifier with Kernel Support Vector Machine by the SGD optimization.
|
48
|
+
#
|
49
|
+
# @param reg_param [Float] The regularization parameter.
|
50
|
+
# @param max_iter [Integer] The maximum number of iterations.
|
51
|
+
# @param probability [Boolean] The flag indicating whether to perform probability estimation.
|
52
|
+
# @param n_jobs [Integer] The number of jobs for running the fit and predict methods in parallel.
|
53
|
+
# If nil is given, the methods do not execute in parallel.
|
54
|
+
# If zero or less is given, it becomes equal to the number of processors.
|
55
|
+
# This parameter is ignored if the Parallel gem is not loaded.
|
56
|
+
# @param random_seed [Integer] The seed value using to initialize the random generator.
|
57
|
+
def initialize(reg_param: 1.0, max_iter: 1000, probability: false, n_jobs: nil, random_seed: nil)
|
58
|
+
super()
|
59
|
+
@params = {
|
60
|
+
reg_param: reg_param,
|
61
|
+
max_iter: max_iter,
|
62
|
+
probability: probability,
|
63
|
+
n_jobs: n_jobs,
|
64
|
+
random_seed: (random_seed || srand)
|
65
|
+
}
|
66
|
+
@rng = Random.new(@params[:random_seed])
|
67
|
+
end
|
68
|
+
|
69
|
+
# Fit the model with given training data.
|
70
|
+
#
|
71
|
+
# @param x [Numo::DFloat] (shape: [n_training_samples, n_training_samples])
|
72
|
+
# The kernel matrix of the training data to be used for fitting the model.
|
73
|
+
# @param y [Numo::Int32] (shape: [n_training_samples]) The labels to be used for fitting the model.
|
74
|
+
# @return [KernelSVC] The learned classifier itself.
|
75
|
+
def fit(x, y)
|
76
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
77
|
+
y = ::Rumale::Validation.check_convert_label_array(y)
|
78
|
+
::Rumale::Validation.check_sample_size(x, y)
|
79
|
+
|
80
|
+
@classes = Numo::Int32[*y.to_a.uniq.sort]
|
81
|
+
n_classes = @classes.size
|
82
|
+
n_features = x.shape[1]
|
83
|
+
|
84
|
+
if n_classes > 2
|
85
|
+
@weight_vec = Numo::DFloat.zeros(n_classes, n_features)
|
86
|
+
@prob_param = Numo::DFloat.zeros(n_classes, 2)
|
87
|
+
models = if enable_parallel?
|
88
|
+
parallel_map(n_classes) do |n|
|
89
|
+
bin_y = Numo::Int32.cast(y.eq(@classes[n])) * 2 - 1
|
90
|
+
partial_fit(x, bin_y)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
Array.new(n_classes) do |n|
|
94
|
+
bin_y = Numo::Int32.cast(y.eq(@classes[n])) * 2 - 1
|
95
|
+
partial_fit(x, bin_y)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
models.each_with_index { |model, n| @weight_vec[n, true], @prob_param[n, true] = model }
|
99
|
+
else
|
100
|
+
negative_label = y.to_a.uniq.min
|
101
|
+
bin_y = Numo::Int32.cast(y.ne(negative_label)) * 2 - 1
|
102
|
+
@weight_vec, @prob_param = partial_fit(x, bin_y)
|
103
|
+
end
|
104
|
+
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
# Calculate confidence scores for samples.
|
109
|
+
#
|
110
|
+
# @param x [Numo::DFloat] (shape: [n_testing_samples, n_training_samples])
|
111
|
+
# The kernel matrix between testing samples and training samples to compute the scores.
|
112
|
+
# @return [Numo::DFloat] (shape: [n_testing_samples, n_classes]) Confidence score per sample.
|
113
|
+
def decision_function(x)
|
114
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
115
|
+
|
116
|
+
x.dot(@weight_vec.transpose)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Predict class labels for samples.
|
120
|
+
#
|
121
|
+
# @param x [Numo::DFloat] (shape: [n_testing_samples, n_training_samples])
|
122
|
+
# The kernel matrix between testing samples and training samples to predict the labels.
|
123
|
+
# @return [Numo::Int32] (shape: [n_testing_samples]) Predicted class label per sample.
|
124
|
+
def predict(x)
|
125
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
126
|
+
|
127
|
+
return Numo::Int32.cast(decision_function(x).ge(0.0)) * 2 - 1 if @classes.size <= 2
|
128
|
+
|
129
|
+
n_samples, = x.shape
|
130
|
+
decision_values = decision_function(x)
|
131
|
+
predicted = if enable_parallel?
|
132
|
+
parallel_map(n_samples) { |n| @classes[decision_values[n, true].max_index] }
|
133
|
+
else
|
134
|
+
Array.new(n_samples) { |n| @classes[decision_values[n, true].max_index] }
|
135
|
+
end
|
136
|
+
Numo::Int32.asarray(predicted)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Predict probability for samples.
|
140
|
+
#
|
141
|
+
# @param x [Numo::DFloat] (shape: [n_testing_samples, n_training_samples])
|
142
|
+
# The kernel matrix between testing samples and training samples to predict the labels.
|
143
|
+
# @return [Numo::DFloat] (shape: [n_samples, n_classes]) Predicted probability of each class per sample.
|
144
|
+
def predict_proba(x)
|
145
|
+
x = ::Rumale::Validation.check_convert_sample_array(x)
|
146
|
+
|
147
|
+
if @classes.size > 2
|
148
|
+
probs = 1.0 / (Numo::NMath.exp(@prob_param[true, 0] * decision_function(x) + @prob_param[true, 1]) + 1.0)
|
149
|
+
return (probs.transpose / probs.sum(axis: 1)).transpose.dup
|
150
|
+
end
|
151
|
+
|
152
|
+
n_samples, = x.shape
|
153
|
+
probs = Numo::DFloat.zeros(n_samples, 2)
|
154
|
+
probs[true, 1] = 1.0 / (Numo::NMath.exp(@prob_param[0] * decision_function(x) + @prob_param[1]) + 1.0)
|
155
|
+
probs[true, 0] = 1.0 - probs[true, 1]
|
156
|
+
probs
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def partial_fit(x, bin_y)
|
162
|
+
# Initialize some variables.
|
163
|
+
n_training_samples = x.shape[0]
|
164
|
+
rand_ids = []
|
165
|
+
weight_vec = Numo::DFloat.zeros(n_training_samples)
|
166
|
+
sub_rng = @rng.dup
|
167
|
+
# Start optimization.
|
168
|
+
@params[:max_iter].times do |t|
|
169
|
+
# random sampling
|
170
|
+
rand_ids = Array(0...n_training_samples).shuffle(random: sub_rng) if rand_ids.empty?
|
171
|
+
target_id = rand_ids.shift
|
172
|
+
# update the weight vector
|
173
|
+
func = (weight_vec * bin_y).dot(x[target_id, true].transpose).to_f
|
174
|
+
func *= bin_y[target_id] / (@params[:reg_param] * (t + 1))
|
175
|
+
weight_vec[target_id] += 1.0 if func < 1.0
|
176
|
+
end
|
177
|
+
w = weight_vec * bin_y
|
178
|
+
p = if @params[:probability]
|
179
|
+
::Rumale::ProbabilisticOutput.fit_sigmoid(x.dot(w), bin_y)
|
180
|
+
else
|
181
|
+
Numo::DFloat[1, 0]
|
182
|
+
end
|
183
|
+
[w, p]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Rumale is a machine learning library in Ruby.
|
4
|
+
module Rumale
|
5
|
+
# This module consists of the classes that implement kernel method-based estimator.
|
6
|
+
module KernelMachine
|
7
|
+
# @!visibility private
|
8
|
+
VERSION = '0.24.0'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'numo/narray'
|
4
|
+
|
5
|
+
require_relative 'kernel_machine/kernel_fda'
|
6
|
+
require_relative 'kernel_machine/kernel_pca'
|
7
|
+
require_relative 'kernel_machine/kernel_ridge'
|
8
|
+
require_relative 'kernel_machine/kernel_ridge_classifier'
|
9
|
+
require_relative 'kernel_machine/kernel_svc'
|
10
|
+
require_relative 'kernel_machine/version'
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rumale-kernel_machine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.24.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- yoshoku
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-12-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: numo-narray
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.9.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.9.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rumale-core
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.24.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.24.0
|
41
|
+
description: |
|
42
|
+
Rumale::KernelMachine provides kernel method-based algorithms,
|
43
|
+
such as Kernel Support Vector Machine, Kernel Principal Componenet Analysis, and Kernel Ridge Regression,
|
44
|
+
with Rumale interface.
|
45
|
+
email:
|
46
|
+
- yoshoku@outlook.com
|
47
|
+
executables: []
|
48
|
+
extensions: []
|
49
|
+
extra_rdoc_files: []
|
50
|
+
files:
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- lib/rumale/kernel_machine.rb
|
54
|
+
- lib/rumale/kernel_machine/kernel_fda.rb
|
55
|
+
- lib/rumale/kernel_machine/kernel_pca.rb
|
56
|
+
- lib/rumale/kernel_machine/kernel_ridge.rb
|
57
|
+
- lib/rumale/kernel_machine/kernel_ridge_classifier.rb
|
58
|
+
- lib/rumale/kernel_machine/kernel_svc.rb
|
59
|
+
- lib/rumale/kernel_machine/version.rb
|
60
|
+
homepage: https://github.com/yoshoku/rumale
|
61
|
+
licenses:
|
62
|
+
- BSD-3-Clause
|
63
|
+
metadata:
|
64
|
+
homepage_uri: https://github.com/yoshoku/rumale
|
65
|
+
source_code_uri: https://github.com/yoshoku/rumale/tree/main/rumale-kernel_machine
|
66
|
+
changelog_uri: https://github.com/yoshoku/rumale/blob/main/CHANGELOG.md
|
67
|
+
documentation_uri: https://yoshoku.github.io/rumale/doc/
|
68
|
+
rubygems_mfa_required: 'true'
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubygems_version: 3.3.26
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Rumale::KernelMachine provides kernel method-based algorithms with Rumale
|
88
|
+
interface.
|
89
|
+
test_files: []
|