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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.github/workflows/build.yml +6 -3
  4. data/.github/workflows/coverage.yml +28 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +1 -0
  7. data/CHANGELOG.md +35 -0
  8. data/Gemfile +6 -4
  9. data/LICENSE.txt +1 -1
  10. data/README.md +56 -19
  11. data/ext/rumale/tree.c +24 -12
  12. data/lib/rumale.rb +8 -0
  13. data/lib/rumale/base/base_estimator.rb +5 -3
  14. data/lib/rumale/dataset.rb +7 -3
  15. data/lib/rumale/decomposition/pca.rb +1 -1
  16. data/lib/rumale/ensemble/stacking_classifier.rb +215 -0
  17. data/lib/rumale/ensemble/stacking_regressor.rb +163 -0
  18. data/lib/rumale/ensemble/voting_classifier.rb +126 -0
  19. data/lib/rumale/ensemble/voting_regressor.rb +82 -0
  20. data/lib/rumale/feature_extraction/feature_hasher.rb +1 -1
  21. data/lib/rumale/feature_extraction/hash_vectorizer.rb +1 -1
  22. data/lib/rumale/kernel_approximation/nystroem.rb +29 -9
  23. data/lib/rumale/kernel_machine/kernel_ridge_classifier.rb +92 -0
  24. data/lib/rumale/kernel_machine/kernel_svc.rb +4 -3
  25. data/lib/rumale/linear_model/elastic_net.rb +1 -1
  26. data/lib/rumale/linear_model/lasso.rb +1 -1
  27. data/lib/rumale/linear_model/linear_regression.rb +63 -34
  28. data/lib/rumale/linear_model/logistic_regression.rb +1 -1
  29. data/lib/rumale/linear_model/nnls.rb +137 -0
  30. data/lib/rumale/linear_model/ridge.rb +70 -33
  31. data/lib/rumale/linear_model/svc.rb +4 -3
  32. data/lib/rumale/linear_model/svr.rb +4 -3
  33. data/lib/rumale/metric_learning/mlkr.rb +161 -0
  34. data/lib/rumale/metric_learning/neighbourhood_component_analysis.rb +7 -4
  35. data/lib/rumale/pairwise_metric.rb +1 -1
  36. data/lib/rumale/preprocessing/kernel_calculator.rb +92 -0
  37. data/lib/rumale/validation.rb +13 -1
  38. data/lib/rumale/version.rb +1 -1
  39. data/rumale.gemspec +1 -1
  40. 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) # rubocop:disable Style/ArrayCoercion
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) # rubocop:disable Style/ArrayCoercion
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. This parameter is ignored in the current implementation.
43
- # @param gamma [Float] The parameter of RBF kernel: exp(-gamma * x^2).
44
- # @param n_components [Integer] The number of dimensions of the RBF kernel feature space.
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
- check_params_numeric(gamma: gamma, n_components: n_components)
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) -> RBF
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 = Rumale::PairwiseMetric.rbf_kernel(@components, nil, @params[:gamma])
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 = Rumale::PairwiseMetric.rbf_kernel(x, @components, @params[:gamma])
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
- # Rumale::SVM provides kernel support vector classifier based on LIBSVM.
15
- # If you prefer execution speed, you should use Rumale::SVM::SVC.
16
- # https://github.com/yoshoku/rumale-svm
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::Int32] (shape: [n_samples, n_outputs]) The target values to be used for fitting the model.
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::Int32] (shape: [n_samples, n_outputs]) The target values to be used for fitting the model.
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 or singular value decomposition (SVD).
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 = 'svd', this parameter is ignored.
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 = 'svd', this parameter is ignored.
48
+ # If solver is not 'sgd', this parameter is ignored.
48
49
  # @param momentum [Float] The momentum factor.
49
- # If solver = 'svd', this parameter is ignored.
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 = 'svd', this parameter is ignored.
55
+ # If solver is 'svd', this parameter is ignored.
55
56
  # @param batch_size [Integer] The size of the mini batches.
56
- # If solver = 'svd', this parameter is ignored.
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 = 'svd', this parameter is ignored.
59
- # @param solver [String] The algorithm to calculate weights. ('auto', 'sgd' or 'svd').
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 = 'svd', this parameter is ignored.
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
- load_linalg? ? 'svd' : 'sgd'
85
+ enable_linalg?(warning: false) ? 'svd' : 'sgd'
84
86
  else
85
- solver != 'svd' ? 'sgd' : 'svd'
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::Int32] (shape: [n_samples, n_outputs]) The target values to be used for fitting the model.
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
- is_single_target_vals = y.shape[1].nil?
131
- if @params[:fit_bias]
132
- @weight_vec = is_single_target_vals ? w[0...-1].dup : w[0...-1, true].dup
133
- @bias_term = is_single_target_vals ? w[-1] : w[-1, true].dup
134
- else
135
- @weight_vec = w.dup
136
- @bias_term = is_single_target_vals ? 0 : Numo::DFloat.zeros(y.shape[1])
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
- def fit_sgd(x, y)
141
- n_outputs = y.shape[1].nil? ? 1 : y.shape[1]
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
- if n_outputs > 1
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 fit_bias?
159
- @params[:fit_bias] == true
182
+ def single_target?(y)
183
+ y.ndim == 1
160
184
  end
161
185
 
162
- def load_linalg?
163
- return false if defined?(Numo::Linalg).nil?
164
- return false if Numo::Linalg::VERSION < '0.1.4'
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
- true
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