rumale 0.22.0 → 0.22.5
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 +4 -4
- data/.coveralls.yml +1 -0
- data/.github/workflows/build.yml +6 -3
- data/.github/workflows/coverage.yml +28 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +35 -0
- data/Gemfile +6 -4
- data/LICENSE.txt +1 -1
- data/README.md +56 -19
- data/ext/rumale/tree.c +24 -12
- data/lib/rumale.rb +8 -0
- data/lib/rumale/base/base_estimator.rb +5 -3
- data/lib/rumale/dataset.rb +7 -3
- data/lib/rumale/decomposition/pca.rb +1 -1
- data/lib/rumale/ensemble/stacking_classifier.rb +215 -0
- data/lib/rumale/ensemble/stacking_regressor.rb +163 -0
- data/lib/rumale/ensemble/voting_classifier.rb +126 -0
- data/lib/rumale/ensemble/voting_regressor.rb +82 -0
- data/lib/rumale/feature_extraction/feature_hasher.rb +1 -1
- data/lib/rumale/feature_extraction/hash_vectorizer.rb +1 -1
- data/lib/rumale/kernel_approximation/nystroem.rb +29 -9
- data/lib/rumale/kernel_machine/kernel_ridge_classifier.rb +92 -0
- data/lib/rumale/kernel_machine/kernel_svc.rb +4 -3
- data/lib/rumale/linear_model/elastic_net.rb +1 -1
- data/lib/rumale/linear_model/lasso.rb +1 -1
- data/lib/rumale/linear_model/linear_regression.rb +63 -34
- data/lib/rumale/linear_model/logistic_regression.rb +1 -1
- data/lib/rumale/linear_model/nnls.rb +137 -0
- data/lib/rumale/linear_model/ridge.rb +70 -33
- data/lib/rumale/linear_model/svc.rb +4 -3
- data/lib/rumale/linear_model/svr.rb +4 -3
- data/lib/rumale/metric_learning/mlkr.rb +161 -0
- data/lib/rumale/metric_learning/neighbourhood_component_analysis.rb +7 -4
- data/lib/rumale/pairwise_metric.rb +1 -1
- data/lib/rumale/preprocessing/kernel_calculator.rb +92 -0
- data/lib/rumale/validation.rb +13 -1
- data/lib/rumale/version.rb +1 -1
- data/rumale.gemspec +1 -1
- metadata +14 -4
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rumale/base/base_estimator'
|
4
|
+
require 'rumale/base/regressor'
|
5
|
+
|
6
|
+
module Rumale
|
7
|
+
module Ensemble
|
8
|
+
# VotingRegressor is a class that implements regressor with voting ensemble method.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# estimators = {
|
12
|
+
# rdg: Rumale::LinearModel::Ridge.new(reg_param: 1e-2, random_seed: 1),
|
13
|
+
# mlp: Rumale::NeuralNetwork::MLPRegressor.new(hidden_units: [256], random_seed: 1),
|
14
|
+
# rnd: Rumale::Ensemble::RandomForestRegressor.new(random_seed: 1)
|
15
|
+
# }
|
16
|
+
# weights = { rdg: 0.2, mlp: 0.3, rnd: 0.5 }
|
17
|
+
#
|
18
|
+
# regressor = Rumale::Ensemble::VotingRegressor.new(estimators: estimators, weights: weights, voting: 'soft')
|
19
|
+
# regressor.fit(x_train, y_train)
|
20
|
+
# results = regressor.predict(x_test)
|
21
|
+
#
|
22
|
+
# *Reference*
|
23
|
+
# - Zhou, Z-H., "Ensemble Methods - Foundations and Algorithms," CRC Press Taylor and Francis Group, Chapman and Hall/CRC, 2012.
|
24
|
+
class VotingRegressor
|
25
|
+
include Base::BaseEstimator
|
26
|
+
include Base::Regressor
|
27
|
+
|
28
|
+
# Return the sub-regressors that voted.
|
29
|
+
# @return [Hash<Symbol,Regressor>]
|
30
|
+
attr_reader :estimators
|
31
|
+
|
32
|
+
# Create a new ensembled regressor with voting rule.
|
33
|
+
#
|
34
|
+
# @param estimators [Hash<Symbol,Regressor>] The sub-regressors to vote.
|
35
|
+
# @param weights [Hash<Symbol,Float>] The weight value for each regressor.
|
36
|
+
def initialize(estimators:, weights: nil)
|
37
|
+
check_params_type(Hash, estimators: estimators)
|
38
|
+
check_params_type_or_nil(Hash, weights: weights)
|
39
|
+
@estimators = estimators
|
40
|
+
@n_outputs = nil
|
41
|
+
@params = {}
|
42
|
+
@params[:weights] = weights || estimators.each_key.with_object({}) { |name, w| w[name] = 1.0 }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Fit the model with given training data.
|
46
|
+
#
|
47
|
+
# @param x [Numo::DFloat] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
|
48
|
+
# @param y [Numo::Int32] (shape: [n_samples]) The labels to be used for fitting the model.
|
49
|
+
# @return [VotingRegressor] The learned regressor itself.
|
50
|
+
def fit(x, y)
|
51
|
+
x = check_convert_sample_array(x)
|
52
|
+
y = check_convert_tvalue_array(y)
|
53
|
+
check_sample_tvalue_size(x, y)
|
54
|
+
|
55
|
+
@n_outputs = y.ndim > 1 ? y.shape[1] : 1
|
56
|
+
@estimators.each_key { |name| @estimators[name].fit(x, y) }
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Predict values for samples.
|
62
|
+
#
|
63
|
+
# @param x [Numo::DFloat] (shape: [n_samples, n_features]) The samples to predict the values.
|
64
|
+
# @return [Numo::DFloat] (shape: [n_samples, n_outputs]) Predicted value per sample.
|
65
|
+
def predict(x)
|
66
|
+
x = check_convert_sample_array(x)
|
67
|
+
z = single_target? ? Numo::DFloat.zeros(x.shape[0]) : Numo::DFloat.zeros(x.shape[0], @n_outputs)
|
68
|
+
sum_weight = @params[:weights].each_value.inject(&:+)
|
69
|
+
@estimators.each do |name, estimator|
|
70
|
+
z += @params[:weights][name] * estimator.predict(x)
|
71
|
+
end
|
72
|
+
z / sum_weight
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def single_target?
|
78
|
+
@n_outputs == 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -67,7 +67,7 @@ module Rumale
|
|
67
67
|
def transform(x)
|
68
68
|
raise 'FeatureHasher#transform requires Mmh3 but that is not loaded.' unless enable_mmh3?
|
69
69
|
|
70
|
-
x = [x] unless x.is_a?(Array)
|
70
|
+
x = [x] unless x.is_a?(Array)
|
71
71
|
n_samples = x.size
|
72
72
|
|
73
73
|
z = Numo::DFloat.zeros(n_samples, n_features)
|
@@ -99,7 +99,7 @@ module Rumale
|
|
99
99
|
# @param x [Array<Hash>] (shape: [n_samples]) The array of hash consisting of feature names and values.
|
100
100
|
# @return [Numo::DFloat] (shape: [n_samples, n_features]) The encoded sample array.
|
101
101
|
def transform(x)
|
102
|
-
x = [x] unless x.is_a?(Array)
|
102
|
+
x = [x] unless x.is_a?(Array)
|
103
103
|
n_samples = x.size
|
104
104
|
n_features = @vocabulary.size
|
105
105
|
z = Numo::DFloat.zeros(n_samples, n_features)
|
@@ -11,7 +11,7 @@ module Rumale
|
|
11
11
|
# @example
|
12
12
|
# require 'numo/linalg/autoloader'
|
13
13
|
#
|
14
|
-
# transformer = Rumale::KernelApproximation::Nystroem.new(gamma: 1, n_components: 128, random_seed: 1)
|
14
|
+
# transformer = Rumale::KernelApproximation::Nystroem.new(kernel: 'rbf', gamma: 1, n_components: 128, random_seed: 1)
|
15
15
|
# new_training_samples = transformer.fit_transform(training_samples)
|
16
16
|
# new_testing_samples = transformer.transform(testing_samples)
|
17
17
|
#
|
@@ -39,12 +39,15 @@ module Rumale
|
|
39
39
|
|
40
40
|
# Create a new transformer for mapping to kernel feature space with Nystrom method.
|
41
41
|
#
|
42
|
-
# @param kernel [String] The type of kernel
|
43
|
-
# @param gamma [Float] The parameter
|
44
|
-
# @param
|
42
|
+
# @param kernel [String] The type of kernel function ('rbf', 'linear', 'poly', and 'sigmoid)
|
43
|
+
# @param gamma [Float] The gamma parameter in rbf/poly/sigmoid kernel function.
|
44
|
+
# @param degree [Integer] The degree parameter in polynomial kernel function.
|
45
|
+
# @param coef [Float] The coefficient in poly/sigmoid kernel function.
|
46
|
+
# @param n_components [Integer] The number of dimensions of the kernel feature space.
|
45
47
|
# @param random_seed [Integer] The seed value using to initialize the random generator.
|
46
|
-
def initialize(kernel: 'rbf', gamma: 1, n_components: 100, random_seed: nil)
|
47
|
-
|
48
|
+
def initialize(kernel: 'rbf', gamma: 1, degree: 3, coef: 1, n_components: 100, random_seed: nil)
|
49
|
+
check_params_string(kernel: kernel)
|
50
|
+
check_params_numeric(gamma: gamma, coef: coef, degree: degree, n_components: n_components)
|
48
51
|
check_params_numeric_or_nil(random_seed: random_seed)
|
49
52
|
@params = method(:initialize).parameters.map { |_t, arg| [arg, binding.local_variable_get(arg)] }.to_h
|
50
53
|
@params[:random_seed] ||= srand
|
@@ -56,7 +59,7 @@ module Rumale
|
|
56
59
|
|
57
60
|
# Fit the model with given training data.
|
58
61
|
#
|
59
|
-
# @overload fit(x) ->
|
62
|
+
# @overload fit(x) -> Nystroem
|
60
63
|
# @param x [Numo::NArray] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
|
61
64
|
# @return [Nystroem] The learned transformer itself.
|
62
65
|
def fit(x, _y = nil)
|
@@ -73,7 +76,7 @@ module Rumale
|
|
73
76
|
@components = x[@component_indices, true]
|
74
77
|
|
75
78
|
# calculate normalizing factor.
|
76
|
-
kernel_mat =
|
79
|
+
kernel_mat = kernel_mat(@components)
|
77
80
|
eig_vals, eig_vecs = Numo::Linalg.eigh(kernel_mat)
|
78
81
|
la = eig_vals.class.maximum(eig_vals.reverse, 1e-12)
|
79
82
|
u = eig_vecs.reverse(1)
|
@@ -98,9 +101,26 @@ module Rumale
|
|
98
101
|
# @return [Numo::DFloat] (shape: [n_samples, n_components]) The transformed data.
|
99
102
|
def transform(x)
|
100
103
|
x = check_convert_sample_array(x)
|
101
|
-
z =
|
104
|
+
z = kernel_mat(x, @components)
|
102
105
|
z.dot(@normalizer)
|
103
106
|
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def kernel_mat(x, y = nil)
|
111
|
+
case @params[:kernel]
|
112
|
+
when 'rbf'
|
113
|
+
Rumale::PairwiseMetric.rbf_kernel(x, y, @params[:gamma])
|
114
|
+
when 'poly'
|
115
|
+
Rumale::PairwiseMetric.polynomial_kernel(x, y, @params[:degree], @params[:gamma], @params[:coef])
|
116
|
+
when 'sigmoid'
|
117
|
+
Rumale::PairwiseMetric.sigmoid_kernel(x, y, @params[:gamma], @params[:coef])
|
118
|
+
when 'linear'
|
119
|
+
Rumale::PairwiseMetric.linear_kernel(x, y)
|
120
|
+
else
|
121
|
+
raise ArgumentError, "Expect kernel parameter to be given 'rbf', 'linear', 'poly', or 'sigmoid'."
|
122
|
+
end
|
123
|
+
end
|
104
124
|
end
|
105
125
|
end
|
106
126
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rumale/base/base_estimator'
|
4
|
+
require 'rumale/base/classifier'
|
5
|
+
require 'rumale/preprocessing/label_binarizer'
|
6
|
+
|
7
|
+
module Rumale
|
8
|
+
module KernelMachine
|
9
|
+
# KernelRidgeClassifier is a class that implements classifier based-on kernel ridge regression.
|
10
|
+
# It learns a classifier by converting labels to target values { -1, 1 } and performing kernel ridge regression.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# require 'numo/linalg/autoloader'
|
14
|
+
# require 'rumale'
|
15
|
+
#
|
16
|
+
# kernel_mat_train = Rumale::PairwiseMetric::rbf_kernel(training_samples)
|
17
|
+
# kridge = Rumale::KernelMachine::KernelRidgeClassifier.new(reg_param: 0.5)
|
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 KernelRidgeClassifier
|
23
|
+
include Base::BaseEstimator
|
24
|
+
include Base::Classifier
|
25
|
+
|
26
|
+
# Return the class labels.
|
27
|
+
# @return [Numo::Int32] (size: n_classes)
|
28
|
+
attr_reader :classes
|
29
|
+
|
30
|
+
# Return the weight vector.
|
31
|
+
# @return [Numo::DFloat] (shape: [n_training_sample, n_classes])
|
32
|
+
attr_reader :weight_vec
|
33
|
+
|
34
|
+
# Create a new regressor with kernel ridge classifier.
|
35
|
+
#
|
36
|
+
# @param reg_param [Float/Numo::DFloat] The regularization parameter.
|
37
|
+
def initialize(reg_param: 1.0)
|
38
|
+
@params = {}
|
39
|
+
@params[:reg_param] = reg_param
|
40
|
+
@classes = nil
|
41
|
+
@weight_vec = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Fit the model with given training data.
|
45
|
+
#
|
46
|
+
# @param x [Numo::DFloat] (shape: [n_training_samples, n_training_samples])
|
47
|
+
# The kernel matrix of the training data to be used for fitting the model.
|
48
|
+
# @param y [Numo::Int32] (shape: [n_training_samples]) The labels to be used for fitting the model.
|
49
|
+
# @return [KernelRidgeClassifier] The learned classifier itself.
|
50
|
+
def fit(x, y)
|
51
|
+
x = check_convert_sample_array(x)
|
52
|
+
y = check_convert_label_array(y)
|
53
|
+
check_sample_label_size(x, y)
|
54
|
+
raise ArgumentError, 'Expect the kernel matrix of training data to be square.' unless x.shape[0] == x.shape[1]
|
55
|
+
raise 'KernelRidgeClassifier#fit requires Numo::Linalg but that is not loaded.' unless enable_linalg?
|
56
|
+
|
57
|
+
@encoder = Rumale::Preprocessing::LabelBinarizer.new
|
58
|
+
y_encoded = Numo::DFloat.cast(@encoder.fit_transform(y)) * 2 - 1
|
59
|
+
@classes = Numo::NArray[*@encoder.classes]
|
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 = check_convert_sample_array(x)
|
75
|
+
x.dot(@weight_vec)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Predict class labels for samples.
|
79
|
+
#
|
80
|
+
# @param x [Numo::DFloat] (shape: [n_testing_samples, n_training_samples])
|
81
|
+
# The kernel matrix between testing samples and training samples to predict the labels.
|
82
|
+
# @return [Numo::Int32] (shape: [n_testing_samples]) Predicted class label per sample.
|
83
|
+
def predict(x)
|
84
|
+
x = check_convert_sample_array(x)
|
85
|
+
scores = decision_function(x)
|
86
|
+
n_samples, n_classes = scores.shape
|
87
|
+
label_ids = scores.max_index(axis: 1) - Numo::Int32.new(n_samples).seq * n_classes
|
88
|
+
@classes[label_ids].dup
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -11,9 +11,10 @@ module Rumale
|
|
11
11
|
# with stochastic gradient descent (SGD) optimization.
|
12
12
|
# For multiclass classification problem, it uses one-vs-the-rest strategy.
|
13
13
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
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
|
17
18
|
#
|
18
19
|
# @example
|
19
20
|
# training_kernel_matrix = Rumale::PairwiseMetric::rbf_kernel(training_samples)
|
@@ -81,7 +81,7 @@ module Rumale
|
|
81
81
|
# Fit the model with given training data.
|
82
82
|
#
|
83
83
|
# @param x [Numo::DFloat] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
|
84
|
-
# @param y [Numo::
|
84
|
+
# @param y [Numo::DFloat] (shape: [n_samples, n_outputs]) The target values to be used for fitting the model.
|
85
85
|
# @return [ElasticNet] The learned regressor itself.
|
86
86
|
def fit(x, y)
|
87
87
|
x = check_convert_sample_array(x)
|
@@ -77,7 +77,7 @@ module Rumale
|
|
77
77
|
# Fit the model with given training data.
|
78
78
|
#
|
79
79
|
# @param x [Numo::DFloat] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
|
80
|
-
# @param y [Numo::
|
80
|
+
# @param y [Numo::DFloat] (shape: [n_samples, n_outputs]) The target values to be used for fitting the model.
|
81
81
|
# @return [Lasso] The learned regressor itself.
|
82
82
|
def fit(x, y)
|
83
83
|
x = check_convert_sample_array(x)
|
@@ -6,7 +6,8 @@ require 'rumale/base/regressor'
|
|
6
6
|
module Rumale
|
7
7
|
module LinearModel
|
8
8
|
# LinearRegression is a class that implements ordinary least square linear regression
|
9
|
-
# with stochastic gradient descent (SGD) optimization
|
9
|
+
# with stochastic gradient descent (SGD) optimization,
|
10
|
+
# singular value decomposition (SVD), or L-BFGS optimization.
|
10
11
|
#
|
11
12
|
# @example
|
12
13
|
# estimator =
|
@@ -41,31 +42,32 @@ module Rumale
|
|
41
42
|
#
|
42
43
|
# @param learning_rate [Float] The initial value of learning rate.
|
43
44
|
# The learning rate decreases as the iteration proceeds according to the equation: learning_rate / (1 + decay * t).
|
44
|
-
# If solver
|
45
|
+
# If solver is not 'sgd', this parameter is ignored.
|
45
46
|
# @param decay [Float] The smoothing parameter for decreasing learning rate as the iteration proceeds.
|
46
47
|
# If nil is given, the decay sets to 'learning_rate'.
|
47
|
-
# If solver
|
48
|
+
# If solver is not 'sgd', this parameter is ignored.
|
48
49
|
# @param momentum [Float] The momentum factor.
|
49
|
-
# If solver
|
50
|
+
# If solver is not 'sgd', this parameter is ignored.
|
50
51
|
# @param fit_bias [Boolean] The flag indicating whether to fit the bias term.
|
51
52
|
# @param bias_scale [Float] The scale of the bias term.
|
52
53
|
# @param max_iter [Integer] The maximum number of epochs that indicates
|
53
54
|
# how many times the whole data is given to the training process.
|
54
|
-
# If solver
|
55
|
+
# If solver is 'svd', this parameter is ignored.
|
55
56
|
# @param batch_size [Integer] The size of the mini batches.
|
56
|
-
# If solver
|
57
|
+
# If solver is not 'sgd', this parameter is ignored.
|
57
58
|
# @param tol [Float] The tolerance of loss for terminating optimization.
|
58
|
-
# If solver
|
59
|
-
# @param solver [String] The algorithm to calculate weights. ('auto', 'sgd' or '
|
59
|
+
# If solver is 'svd', this parameter is ignored.
|
60
|
+
# @param solver [String] The algorithm to calculate weights. ('auto', 'sgd', 'svd' or 'lbfgs').
|
60
61
|
# 'auto' chooses the 'svd' solver if Numo::Linalg is loaded. Otherwise, it chooses the 'sgd' solver.
|
61
62
|
# 'sgd' uses the stochastic gradient descent optimization.
|
62
63
|
# 'svd' performs singular value decomposition of samples.
|
64
|
+
# 'lbfgs' uses the L-BFGS method for optimization.
|
63
65
|
# @param n_jobs [Integer] The number of jobs for running the fit method in parallel.
|
64
66
|
# If nil is given, the method does not execute in parallel.
|
65
67
|
# If zero or less is given, it becomes equal to the number of processors.
|
66
|
-
# This parameter is ignored if the Parallel gem is not loaded.
|
68
|
+
# This parameter is ignored if the Parallel gem is not loaded or solver is not 'sgd'.
|
67
69
|
# @param verbose [Boolean] The flag indicating whether to output loss during iteration.
|
68
|
-
# If solver
|
70
|
+
# If solver is 'svd', this parameter is ignored.
|
69
71
|
# @param random_seed [Integer] The seed value using to initialize the random generator.
|
70
72
|
def initialize(learning_rate: 0.01, decay: nil, momentum: 0.9,
|
71
73
|
fit_bias: true, bias_scale: 1.0, max_iter: 1000, batch_size: 50, tol: 1e-4,
|
@@ -80,9 +82,9 @@ module Rumale
|
|
80
82
|
super()
|
81
83
|
@params.merge!(method(:initialize).parameters.map { |_t, arg| [arg, binding.local_variable_get(arg)] }.to_h)
|
82
84
|
@params[:solver] = if solver == 'auto'
|
83
|
-
|
85
|
+
enable_linalg?(warning: false) ? 'svd' : 'sgd'
|
84
86
|
else
|
85
|
-
solver
|
87
|
+
solver.match?(/^svd$|^sgd$|^lbfgs$/) ? solver : 'sgd'
|
86
88
|
end
|
87
89
|
@params[:decay] ||= @params[:learning_rate]
|
88
90
|
@params[:random_seed] ||= srand
|
@@ -95,15 +97,17 @@ module Rumale
|
|
95
97
|
# Fit the model with given training data.
|
96
98
|
#
|
97
99
|
# @param x [Numo::DFloat] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
|
98
|
-
# @param y [Numo::
|
100
|
+
# @param y [Numo::DFloat] (shape: [n_samples, n_outputs]) The target values to be used for fitting the model.
|
99
101
|
# @return [LinearRegression] The learned regressor itself.
|
100
102
|
def fit(x, y)
|
101
103
|
x = check_convert_sample_array(x)
|
102
104
|
y = check_convert_tvalue_array(y)
|
103
105
|
check_sample_tvalue_size(x, y)
|
104
106
|
|
105
|
-
if @params[:solver] == 'svd' && enable_linalg?
|
107
|
+
if @params[:solver] == 'svd' && enable_linalg?(warning: false)
|
106
108
|
fit_svd(x, y)
|
109
|
+
elsif @params[:solver] == 'lbfgs'
|
110
|
+
fit_lbfgs(x, y)
|
107
111
|
else
|
108
112
|
fit_sgd(x, y)
|
109
113
|
end
|
@@ -124,24 +128,46 @@ module Rumale
|
|
124
128
|
|
125
129
|
def fit_svd(x, y)
|
126
130
|
x = expand_feature(x) if fit_bias?
|
127
|
-
|
128
131
|
w = Numo::Linalg.pinv(x, driver: 'svd').dot(y)
|
132
|
+
@weight_vec, @bias_term = single_target?(y) ? split_weight(w) : split_weight_mult(w)
|
133
|
+
end
|
129
134
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
135
|
+
def fit_lbfgs(x, y)
|
136
|
+
fnc = proc do |w, x, y| # rubocop:disable Lint/ShadowingOuterLocalVariable
|
137
|
+
n_samples, n_features = x.shape
|
138
|
+
w = w.reshape(y.shape[1], n_features) unless y.shape[1].nil?
|
139
|
+
z = x.dot(w.transpose)
|
140
|
+
d = z - y
|
141
|
+
loss = (d**2).sum.fdiv(n_samples)
|
142
|
+
gradient = 2.fdiv(n_samples) * d.transpose.dot(x)
|
143
|
+
[loss, gradient.flatten.dup]
|
137
144
|
end
|
138
|
-
end
|
139
145
|
|
140
|
-
|
141
|
-
|
146
|
+
x = expand_feature(x) if fit_bias?
|
147
|
+
|
142
148
|
n_features = x.shape[1]
|
149
|
+
n_outputs = single_target?(y) ? 1 : y.shape[1]
|
150
|
+
|
151
|
+
res = Lbfgsb.minimize(
|
152
|
+
fnc: fnc, jcb: true, x_init: init_weight(n_features, n_outputs), args: [x, y],
|
153
|
+
maxiter: @params[:max_iter], factr: @params[:tol] / Lbfgsb::DBL_EPSILON,
|
154
|
+
verbose: @params[:verbose] ? 1 : -1
|
155
|
+
)
|
156
|
+
|
157
|
+
@weight_vec, @bias_term =
|
158
|
+
if single_target?(y)
|
159
|
+
split_weight(res[:x])
|
160
|
+
else
|
161
|
+
split_weight_mult(res[:x].reshape(n_outputs, n_features).transpose)
|
162
|
+
end
|
163
|
+
end
|
143
164
|
|
144
|
-
|
165
|
+
def fit_sgd(x, y)
|
166
|
+
if single_target?(y)
|
167
|
+
@weight_vec, @bias_term = partial_fit(x, y)
|
168
|
+
else
|
169
|
+
n_outputs = y.shape[1]
|
170
|
+
n_features = x.shape[1]
|
145
171
|
@weight_vec = Numo::DFloat.zeros(n_outputs, n_features)
|
146
172
|
@bias_term = Numo::DFloat.zeros(n_outputs)
|
147
173
|
if enable_parallel?
|
@@ -150,20 +176,23 @@ module Rumale
|
|
150
176
|
else
|
151
177
|
n_outputs.times { |n| @weight_vec[n, true], @bias_term[n] = partial_fit(x, y[true, n]) }
|
152
178
|
end
|
153
|
-
else
|
154
|
-
@weight_vec, @bias_term = partial_fit(x, y)
|
155
179
|
end
|
156
180
|
end
|
157
181
|
|
158
|
-
def
|
159
|
-
|
182
|
+
def single_target?(y)
|
183
|
+
y.ndim == 1
|
160
184
|
end
|
161
185
|
|
162
|
-
def
|
163
|
-
|
164
|
-
|
186
|
+
def init_weight(n_features, n_outputs)
|
187
|
+
Rumale::Utils.rand_normal([n_outputs, n_features], @rng.dup).flatten.dup
|
188
|
+
end
|
165
189
|
|
166
|
-
|
190
|
+
def split_weight_mult(w)
|
191
|
+
if fit_bias?
|
192
|
+
[w[0...-1, true].dup, w[-1, true].dup]
|
193
|
+
else
|
194
|
+
[w.dup, Numo::DFloat.zeros(w.shape[1])]
|
195
|
+
end
|
167
196
|
end
|
168
197
|
end
|
169
198
|
end
|