rumale-linear_model 0.24.0 → 0.26.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.
@@ -2,21 +2,21 @@
2
2
 
3
3
  require 'lbfgsb'
4
4
 
5
+ require 'rumale/base/estimator'
5
6
  require 'rumale/base/regressor'
6
7
  require 'rumale/validation'
7
- require 'rumale/linear_model/base_sgd'
8
+
9
+ require_relative 'base_estimator'
8
10
 
9
11
  module Rumale
10
12
  module LinearModel
11
13
  # LinearRegression is a class that implements ordinary least square linear regression
12
- # with stochastic gradient descent (SGD) optimization,
13
- # singular value decomposition (SVD), or L-BFGS optimization.
14
+ # with singular value decomposition (SVD) or L-BFGS optimization.
14
15
  #
15
16
  # @example
16
17
  # require 'rumale/linear_model/linear_regression'
17
18
  #
18
- # estimator =
19
- # Rumale::LinearModel::LinearRegression.new(max_iter: 1000, batch_size: 20, random_seed: 1)
19
+ # estimator = Rumale::LinearModel::LinearRegression.new
20
20
  # estimator.fit(training_samples, traininig_values)
21
21
  # results = estimator.predict(testing_samples)
22
22
  #
@@ -27,70 +27,38 @@ module Rumale
27
27
  # estimator = Rumale::LinearModel::LinearRegression.new(solver: 'svd')
28
28
  # estimator.fit(training_samples, traininig_values)
29
29
  # results = estimator.predict(testing_samples)
30
- #
31
- # *Reference*
32
- # - Bottou, L., "Large-Scale Machine Learning with Stochastic Gradient Descent," Proc. COMPSTAT'10, pp. 177--186, 2010.
33
- class LinearRegression < BaseSGD
34
- include ::Rumale::Base::Regressor
35
-
36
- # Return the weight vector.
37
- # @return [Numo::DFloat] (shape: [n_outputs, n_features])
38
- attr_reader :weight_vec
39
-
40
- # Return the bias term (a.k.a. intercept).
41
- # @return [Numo::DFloat] (shape: [n_outputs])
42
- attr_reader :bias_term
43
-
44
- # Return the random generator for random sampling.
45
- # @return [Random]
46
- attr_reader :rng
30
+ class LinearRegression < Rumale::LinearModel::BaseEstimator
31
+ include Rumale::Base::Regressor
47
32
 
48
33
  # Create a new ordinary least square linear regressor.
49
34
  #
50
- # @param learning_rate [Float] The initial value of learning rate.
51
- # The learning rate decreases as the iteration proceeds according to the equation: learning_rate / (1 + decay * t).
52
- # If solver is not 'sgd', this parameter is ignored.
53
- # @param decay [Float] The smoothing parameter for decreasing learning rate as the iteration proceeds.
54
- # If nil is given, the decay sets to 'learning_rate'.
55
- # If solver is not 'sgd', this parameter is ignored.
56
- # @param momentum [Float] The momentum factor.
57
- # If solver is not 'sgd', this parameter is ignored.
58
35
  # @param fit_bias [Boolean] The flag indicating whether to fit the bias term.
59
36
  # @param bias_scale [Float] The scale of the bias term.
60
37
  # @param max_iter [Integer] The maximum number of epochs that indicates
61
38
  # how many times the whole data is given to the training process.
62
39
  # If solver is 'svd', this parameter is ignored.
63
- # @param batch_size [Integer] The size of the mini batches.
64
- # If solver is not 'sgd', this parameter is ignored.
65
40
  # @param tol [Float] The tolerance of loss for terminating optimization.
66
41
  # If solver is 'svd', this parameter is ignored.
67
- # @param solver [String] The algorithm to calculate weights. ('auto', 'sgd', 'svd' or 'lbfgs').
42
+ # @param solver [String] The algorithm to calculate weights. ('auto', 'svd' or 'lbfgs').
68
43
  # 'auto' chooses the 'svd' solver if Numo::Linalg is loaded. Otherwise, it chooses the 'lbfgs' solver.
69
- # 'sgd' uses the stochastic gradient descent optimization.
70
44
  # 'svd' performs singular value decomposition of samples.
71
45
  # 'lbfgs' uses the L-BFGS method for optimization.
72
- # @param n_jobs [Integer] The number of jobs for running the fit method in parallel.
73
- # If nil is given, the method does not execute in parallel.
74
- # If zero or less is given, it becomes equal to the number of processors.
75
- # This parameter is ignored if the Parallel gem is not loaded or solver is not 'sgd'.
76
46
  # @param verbose [Boolean] The flag indicating whether to output loss during iteration.
77
47
  # If solver is 'svd', this parameter is ignored.
78
- # @param random_seed [Integer] The seed value using to initialize the random generator.
79
- def initialize(learning_rate: 0.01, decay: nil, momentum: 0.9,
80
- fit_bias: true, bias_scale: 1.0, max_iter: 1000, batch_size: 50, tol: 1e-4,
81
- solver: 'auto',
82
- n_jobs: nil, verbose: false, random_seed: nil)
48
+ def initialize(fit_bias: true, bias_scale: 1.0, max_iter: 1000, tol: 1e-4, solver: 'auto', verbose: false)
83
49
  super()
84
- @params.merge!(method(:initialize).parameters.to_h { |_t, arg| [arg, binding.local_variable_get(arg)] })
50
+ @params = {
51
+ fit_bias: fit_bias,
52
+ bias_scale: bias_scale,
53
+ max_iter: max_iter,
54
+ tol: tol,
55
+ verbose: verbose
56
+ }
85
57
  @params[:solver] = if solver == 'auto'
86
58
  enable_linalg?(warning: false) ? 'svd' : 'lbfgs'
87
59
  else
88
- solver.match?(/^svd$|^sgd$|^lbfgs$/) ? solver : 'lbfgs'
60
+ solver.match?(/^svd$|^lbfgs$/) ? solver : 'lbfgs'
89
61
  end
90
- @params[:decay] ||= @params[:learning_rate]
91
- @params[:random_seed] ||= srand
92
- @rng = Random.new(@params[:random_seed])
93
- @loss_func = ::Rumale::LinearModel::Loss::MeanSquaredError.new
94
62
  end
95
63
 
96
64
  # Fit the model with given training data.
@@ -99,17 +67,15 @@ module Rumale
99
67
  # @param y [Numo::DFloat] (shape: [n_samples, n_outputs]) The target values to be used for fitting the model.
100
68
  # @return [LinearRegression] The learned regressor itself.
101
69
  def fit(x, y)
102
- x = ::Rumale::Validation.check_convert_sample_array(x)
103
- y = ::Rumale::Validation.check_convert_target_value_array(y)
104
- ::Rumale::Validation.check_sample_size(x, y)
105
-
106
- if @params[:solver] == 'svd' && enable_linalg?(warning: false)
107
- fit_svd(x, y)
108
- elsif @params[:solver] == 'lbfgs'
109
- fit_lbfgs(x, y)
110
- else
111
- fit_sgd(x, y)
112
- end
70
+ x = Rumale::Validation.check_convert_sample_array(x)
71
+ y = Rumale::Validation.check_convert_target_value_array(y)
72
+ Rumale::Validation.check_sample_size(x, y)
73
+
74
+ @weight_vec, @bias_term = if @params[:solver] == 'svd' && enable_linalg?(warning: false)
75
+ partial_fit_svd(x, y)
76
+ else
77
+ partial_fit_lbfgs(x, y)
78
+ end
113
79
 
114
80
  self
115
81
  end
@@ -119,21 +85,22 @@ module Rumale
119
85
  # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The samples to predict the values.
120
86
  # @return [Numo::DFloat] (shape: [n_samples, n_outputs]) Predicted values per sample.
121
87
  def predict(x)
122
- x = ::Rumale::Validation.check_convert_sample_array(x)
88
+ x = Rumale::Validation.check_convert_sample_array(x)
123
89
 
124
90
  x.dot(@weight_vec.transpose) + @bias_term
125
91
  end
126
92
 
127
93
  private
128
94
 
129
- def fit_svd(x, y)
95
+ def partial_fit_svd(x, y)
130
96
  x = expand_feature(x) if fit_bias?
131
97
  w = Numo::Linalg.pinv(x, driver: 'svd').dot(y)
132
- @weight_vec, @bias_term = single_target?(y) ? split_weight(w) : split_weight_mult(w)
98
+ w = w.transpose.dup unless single_target?(y)
99
+ split_weight(w)
133
100
  end
134
101
 
135
- def fit_lbfgs(x, y)
136
- fnc = proc do |w, x, y| # rubocop:disable Lint/ShadowingOuterLocalVariable
102
+ def partial_fit_lbfgs(base_x, base_y)
103
+ fnc = proc do |w, x, y|
137
104
  n_samples, n_features = x.shape
138
105
  w = w.reshape(y.shape[1], n_features) unless y.shape[1].nil?
139
106
  z = x.dot(w.transpose)
@@ -143,57 +110,25 @@ module Rumale
143
110
  [loss, gradient.flatten.dup]
144
111
  end
145
112
 
146
- x = expand_feature(x) if fit_bias?
113
+ base_x = expand_feature(base_x) if fit_bias?
147
114
 
148
- n_features = x.shape[1]
149
- n_outputs = single_target?(y) ? 1 : y.shape[1]
115
+ n_features = base_x.shape[1]
116
+ n_outputs = single_target?(base_y) ? 1 : base_y.shape[1]
117
+ w_init = Numo::DFloat.zeros(n_outputs * n_features)
150
118
 
151
119
  res = Lbfgsb.minimize(
152
- fnc: fnc, jcb: true, x_init: init_weight(n_features, n_outputs), args: [x, y],
120
+ fnc: fnc, jcb: true, x_init: w_init, args: [base_x, base_y],
153
121
  maxiter: @params[:max_iter], factr: @params[:tol] / Lbfgsb::DBL_EPSILON,
154
122
  verbose: @params[:verbose] ? 1 : -1
155
123
  )
156
124
 
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
164
-
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]
171
- @weight_vec = Numo::DFloat.zeros(n_outputs, n_features)
172
- @bias_term = Numo::DFloat.zeros(n_outputs)
173
- if enable_parallel?
174
- models = parallel_map(n_outputs) { |n| partial_fit(x, y[true, n]) }
175
- n_outputs.times { |n| @weight_vec[n, true], @bias_term[n] = models[n] }
176
- else
177
- n_outputs.times { |n| @weight_vec[n, true], @bias_term[n] = partial_fit(x, y[true, n]) }
178
- end
179
- end
125
+ w = single_target?(base_y) ? res[:x] : res[:x].reshape(n_outputs, n_features)
126
+ split_weight(w)
180
127
  end
181
128
 
182
129
  def single_target?(y)
183
130
  y.ndim == 1
184
131
  end
185
-
186
- def init_weight(n_features, n_outputs)
187
- Rumale::Utils.rand_normal([n_outputs, n_features], @rng.dup).flatten.dup
188
- end
189
-
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
196
- end
197
132
  end
198
133
  end
199
134
  end
@@ -2,16 +2,15 @@
2
2
 
3
3
  require 'lbfgsb'
4
4
 
5
- require 'rumale/base/classifier'
6
5
  require 'rumale/utils'
7
6
  require 'rumale/validation'
8
- require 'rumale/linear_model/base_sgd'
7
+ require 'rumale/base/classifier'
8
+
9
+ require_relative 'base_estimator'
9
10
 
10
11
  module Rumale
11
12
  module LinearModel
12
- # LogisticRegression is a class that implements Logistic Regression.
13
- # In multiclass classification problem, it uses one-vs-the-rest strategy for the sgd solver
14
- # and multinomial logistic regression for the lbfgs solver.
13
+ # LogisticRegression is a class that implements (multinomial) Logistic Regression.
15
14
  #
16
15
  # @note
17
16
  # Rumale::SVM provides Logistic Regression based on LIBLINEAR.
@@ -21,88 +20,42 @@ module Rumale
21
20
  # @example
22
21
  # require 'rumale/linear_model/logistic_regression'
23
22
  #
24
- # estimator =
25
- # Rumale::LinearModel::LogisticRegression.new(reg_param: 1.0, random_seed: 1)
23
+ # estimator = Rumale::LinearModel::LogisticRegression.new(reg_param: 1.0)
26
24
  # estimator.fit(training_samples, traininig_labels)
27
25
  # results = estimator.predict(testing_samples)
28
- #
29
- # *Reference*
30
- # - 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.
31
- # - Tsuruoka, Y., Tsujii, J., and Ananiadou, S., "Stochastic Gradient Descent Training for L1-regularized Log-linear Models with Cumulative Penalty," Proc. ACL'09, pp. 477--485, 2009.
32
- # - Bottou, L., "Large-Scale Machine Learning with Stochastic Gradient Descent," Proc. COMPSTAT'10, pp. 177--186, 2010.
33
- class LogisticRegression < BaseSGD # rubocop:disable Metrics/ClassLength
34
- include ::Rumale::Base::Classifier
35
-
36
- # Return the weight vector for Logistic Regression.
37
- # @return [Numo::DFloat] (shape: [n_classes, n_features])
38
- attr_reader :weight_vec
39
-
40
- # Return the bias term (a.k.a. intercept) for Logistic Regression.
41
- # @return [Numo::DFloat] (shape: [n_classes])
42
- attr_reader :bias_term
26
+ class LogisticRegression < Rumale::LinearModel::BaseEstimator
27
+ include Rumale::Base::Classifier
43
28
 
44
29
  # Return the class labels.
45
30
  # @return [Numo::Int32] (shape: [n_classes])
46
31
  attr_reader :classes
47
32
 
48
- # Return the random generator for performing random sampling.
49
- # @return [Random]
50
- attr_reader :rng
51
-
52
33
  # Create a new classifier with Logisitc Regression.
53
34
  #
54
- # @param learning_rate [Float] The initial value of learning rate.
55
- # The learning rate decreases as the iteration proceeds according to the equation: learning_rate / (1 + decay * t).
56
- # If solver = 'lbfgs', this parameter is ignored.
57
- # @param decay [Float] The smoothing parameter for decreasing learning rate as the iteration proceeds.
58
- # If nil is given, the decay sets to 'reg_param * learning_rate'.
59
- # If solver = 'lbfgs', this parameter is ignored.
60
- # @param momentum [Float] The momentum factor.
61
- # If solver = 'lbfgs', this parameter is ignored.
62
- # @param penalty [String] The regularization type to be used ('l1', 'l2', and 'elasticnet').
63
- # If solver = 'lbfgs', only 'l2' can be selected for this parameter.
64
- # @param l1_ratio [Float] The elastic-net type regularization mixing parameter.
65
- # If penalty set to 'l2' or 'l1', this parameter is ignored.
66
- # If l1_ratio = 1, the regularization is similar to Lasso.
67
- # If l1_ratio = 0, the regularization is similar to Ridge.
68
- # If 0 < l1_ratio < 1, the regularization is a combination of L1 and L2.
69
- # If solver = 'lbfgs', this parameter is ignored.
70
35
  # @param reg_param [Float] The regularization parameter.
71
36
  # @param fit_bias [Boolean] The flag indicating whether to fit the bias term.
72
37
  # @param bias_scale [Float] The scale of the bias term.
73
38
  # If fit_bias is true, the feature vector v becoms [v; bias_scale].
74
39
  # @param max_iter [Integer] The maximum number of epochs that indicates
75
40
  # how many times the whole data is given to the training process.
76
- # @param batch_size [Integer] The size of the mini batches.
77
- # If solver = 'lbfgs', this parameter is ignored.
78
41
  # @param tol [Float] The tolerance of loss for terminating optimization.
79
- # If solver = 'lbfgs', this value is given as tol / Lbfgsb::DBL_EPSILON to the factr argument of Lbfgsb.minimize method.
80
- # @param solver [String] The algorithm for optimization. ('lbfgs' or 'sgd').
81
- # 'lbfgs' uses the L-BFGS with lbfgs.rb gem.
82
- # 'sgd' uses the stochastic gradient descent optimization.
83
- # @param n_jobs [Integer] The number of jobs for running the fit and predict methods in parallel.
42
+ # @param n_jobs [Integer] The number of jobs for running the predict methods in parallel.
84
43
  # If nil is given, the methods do not execute in parallel.
85
44
  # If zero or less is given, it becomes equal to the number of processors.
86
- # This parameter is ignored if the Parallel gem is not loaded or the solver is 'lbfgs'.
45
+ # This parameter is ignored if the Parallel gem is not loaded.
87
46
  # @param verbose [Boolean] The flag indicating whether to output loss during iteration.
88
- # If solver = 'lbfgs' and true is given, 'iterate.dat' file is generated by lbfgsb.rb.
89
- # @param random_seed [Integer] The seed value using to initialize the random generator.
90
- def initialize(learning_rate: 0.01, decay: nil, momentum: 0.9,
91
- penalty: 'l2', reg_param: 1.0, l1_ratio: 0.5,
92
- fit_bias: true, bias_scale: 1.0,
93
- max_iter: 1000, batch_size: 50, tol: 1e-4,
94
- solver: 'lbfgs',
95
- n_jobs: nil, verbose: false, random_seed: nil)
96
- raise ArgumentError, "The 'lbfgs' solver supports only 'l2' penalties." if solver == 'lbfgs' && penalty != 'l2'
97
-
47
+ # 'iterate.dat' file is generated by lbfgsb.rb.
48
+ def initialize(reg_param: 1.0, fit_bias: true, bias_scale: 1.0, max_iter: 1000, tol: 1e-4, n_jobs: nil, verbose: false)
98
49
  super()
99
- @params.merge!(method(:initialize).parameters.to_h { |_t, arg| [arg, binding.local_variable_get(arg)] })
100
- @params[:solver] = solver == 'sgd' ? 'sgd' : 'lbfgs'
101
- @params[:decay] ||= @params[:reg_param] * @params[:learning_rate]
102
- @params[:random_seed] ||= srand
103
- @rng = Random.new(@params[:random_seed])
104
- @penalty_type = @params[:penalty]
105
- @loss_func = ::Rumale::LinearModel::Loss::LogLoss.new
50
+ @params = {
51
+ reg_param: reg_param,
52
+ fit_bias: fit_bias,
53
+ bias_scale: bias_scale,
54
+ max_iter: max_iter,
55
+ tol: tol,
56
+ n_jobs: n_jobs,
57
+ verbose: verbose
58
+ }
106
59
  end
107
60
 
108
61
  # Fit the model with given training data.
@@ -111,16 +64,12 @@ module Rumale
111
64
  # @param y [Numo::Int32] (shape: [n_samples]) The labels to be used for fitting the model.
112
65
  # @return [LogisticRegression] The learned classifier itself.
113
66
  def fit(x, y)
114
- x = ::Rumale::Validation.check_convert_sample_array(x)
115
- y = ::Rumale::Validation.check_convert_label_array(y)
116
- ::Rumale::Validation.check_sample_size(x, y)
67
+ x = Rumale::Validation.check_convert_sample_array(x)
68
+ y = Rumale::Validation.check_convert_label_array(y)
69
+ Rumale::Validation.check_sample_size(x, y)
117
70
 
118
71
  @classes = Numo::Int32[*y.to_a.uniq.sort]
119
- if @params[:solver] == 'sgd'
120
- fit_sgd(x, y)
121
- else
122
- fit_lbfgs(x, y)
123
- end
72
+ @weight_vec, @bias_term = partial_fit(x, y)
124
73
 
125
74
  self
126
75
  end
@@ -130,7 +79,7 @@ module Rumale
130
79
  # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The samples to compute the scores.
131
80
  # @return [Numo::DFloat] (shape: [n_samples, n_classes]) Confidence score per sample.
132
81
  def decision_function(x)
133
- x = ::Rumale::Validation.check_convert_sample_array(x)
82
+ x = Rumale::Validation.check_convert_sample_array(x)
134
83
 
135
84
  x.dot(@weight_vec.transpose) + @bias_term
136
85
  end
@@ -140,7 +89,7 @@ module Rumale
140
89
  # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The samples to predict the labels.
141
90
  # @return [Numo::Int32] (shape: [n_samples]) Predicted class label per sample.
142
91
  def predict(x)
143
- x = ::Rumale::Validation.check_convert_sample_array(x)
92
+ x = Rumale::Validation.check_convert_sample_array(x)
144
93
 
145
94
  n_samples, = x.shape
146
95
  decision_values = predict_proba(x)
@@ -157,7 +106,7 @@ module Rumale
157
106
  # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The samples to predict the probailities.
158
107
  # @return [Numo::DFloat] (shape: [n_samples, n_classes]) Predicted probability of each class per sample.
159
108
  def predict_proba(x)
160
- x = ::Rumale::Validation.check_convert_sample_array(x)
109
+ x = Rumale::Validation.check_convert_sample_array(x)
161
110
 
162
111
  proba = 1.0 / (Numo::NMath.exp(-decision_function(x)) + 1.0)
163
112
  return (proba.transpose / proba.sum(axis: 1)).transpose.dup if multiclass_problem?
@@ -171,11 +120,7 @@ module Rumale
171
120
 
172
121
  private
173
122
 
174
- def multiclass_problem?
175
- @classes.size > 2
176
- end
177
-
178
- def fit_lbfgs(base_x, base_y) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
123
+ def partial_fit(base_x, base_y) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
179
124
  if multiclass_problem?
180
125
  fnc = proc do |w, x, y, a|
181
126
  n_features = x.shape[1]
@@ -199,20 +144,13 @@ module Rumale
199
144
  n_features = base_x.shape[1]
200
145
  w_init = Numo::DFloat.zeros(n_classes * n_features)
201
146
 
202
- verbose = @params[:verbose] ? 1 : -1
203
147
  res = Lbfgsb.minimize(
204
148
  fnc: fnc, jcb: true, x_init: w_init, args: [base_x, onehot_y, @params[:reg_param]],
205
- maxiter: @params[:max_iter], factr: @params[:tol] / Lbfgsb::DBL_EPSILON, verbose: verbose
149
+ maxiter: @params[:max_iter], factr: @params[:tol] / Lbfgsb::DBL_EPSILON,
150
+ verbose: @params[:verbose] ? 1 : -1
206
151
  )
207
152
 
208
- if fit_bias?
209
- weight = res[:x].reshape(n_classes, n_features)
210
- @weight_vec = weight[true, 0...-1].dup
211
- @bias_term = weight[true, -1].dup
212
- else
213
- @weight_vec = res[:x].reshape(n_classes, n_features)
214
- @bias_term = Numo::DFloat.zeros(n_classes)
215
- end
153
+ split_weight(res[:x].reshape(n_classes, n_features))
216
154
  else
217
155
  fnc = proc do |w, x, y, a|
218
156
  z = 1 + Numo::NMath.exp(-y * x.dot(w))
@@ -227,39 +165,18 @@ module Rumale
227
165
  n_features = base_x.shape[1]
228
166
  w_init = Numo::DFloat.zeros(n_features)
229
167
 
230
- verbose = @params[:verbose] ? 1 : -1
231
168
  res = Lbfgsb.minimize(
232
169
  fnc: fnc, jcb: true, x_init: w_init, args: [base_x, bin_y, @params[:reg_param]],
233
- maxiter: @params[:max_iter], factr: @params[:tol] / Lbfgsb::DBL_EPSILON, verbose: verbose
170
+ maxiter: @params[:max_iter], factr: @params[:tol] / Lbfgsb::DBL_EPSILON,
171
+ verbose: @params[:verbose] ? 1 : -1
234
172
  )
235
173
 
236
- @weight_vec, @bias_term = split_weight(res[:x])
174
+ split_weight(res[:x])
237
175
  end
238
176
  end
239
177
 
240
- def fit_sgd(x, y)
241
- if multiclass_problem?
242
- n_classes = @classes.size
243
- n_features = x.shape[1]
244
- @weight_vec = Numo::DFloat.zeros(n_classes, n_features)
245
- @bias_term = Numo::DFloat.zeros(n_classes)
246
- if enable_parallel?
247
- models = parallel_map(n_classes) do |n|
248
- bin_y = Numo::Int32.cast(y.eq(@classes[n])) * 2 - 1
249
- partial_fit(x, bin_y)
250
- end
251
- n_classes.times { |n| @weight_vec[n, true], @bias_term[n] = models[n] }
252
- else
253
- n_classes.times do |n|
254
- bin_y = Numo::Int32.cast(y.eq(@classes[n])) * 2 - 1
255
- @weight_vec[n, true], @bias_term[n] = partial_fit(x, bin_y)
256
- end
257
- end
258
- else
259
- negative_label = @classes[0]
260
- bin_y = Numo::Int32.cast(y.ne(negative_label)) * 2 - 1
261
- @weight_vec, @bias_term = partial_fit(x, bin_y)
262
- end
178
+ def multiclass_problem?
179
+ @classes.size > 2
263
180
  end
264
181
  end
265
182
  end
@@ -2,10 +2,11 @@
2
2
 
3
3
  require 'lbfgsb'
4
4
 
5
- require 'rumale/base/estimator'
6
5
  require 'rumale/base/regressor'
7
6
  require 'rumale/validation'
8
7
 
8
+ require_relative 'base_estimator'
9
+
9
10
  module Rumale
10
11
  module LinearModel
11
12
  # NNLS is a class that implements non-negative least squares regression.
@@ -14,29 +15,17 @@ module Rumale
14
15
  # @example
15
16
  # require 'rumale/linear_model/nnls'
16
17
  #
17
- # estimator = Rumale::LinearModel::NNLS.new(reg_param: 0.01, random_seed: 1)
18
+ # estimator = Rumale::LinearModel::NNLS.new(reg_param: 0.01)
18
19
  # estimator.fit(training_samples, traininig_values)
19
20
  # results = estimator.predict(testing_samples)
20
21
  #
21
- class NNLS < ::Rumale::Base::Estimator
22
- include ::Rumale::Base::Regressor
23
-
24
- # Return the weight vector.
25
- # @return [Numo::DFloat] (shape: [n_outputs, n_features])
26
- attr_reader :weight_vec
27
-
28
- # Return the bias term (a.k.a. intercept).
29
- # @return [Numo::DFloat] (shape: [n_outputs])
30
- attr_reader :bias_term
22
+ class NNLS < Rumale::LinearModel::BaseEstimator
23
+ include Rumale::Base::Regressor
31
24
 
32
25
  # Returns the number of iterations when converged.
33
26
  # @return [Integer]
34
27
  attr_reader :n_iter
35
28
 
36
- # Return the random generator for initializing weight.
37
- # @return [Random]
38
- attr_reader :rng
39
-
40
29
  # Create a new regressor with non-negative least squares method.
41
30
  #
42
31
  # @param reg_param [Float] The regularization parameter for L2 regularization term.
@@ -47,9 +36,7 @@ module Rumale
47
36
  # @param tol [Float] The tolerance of loss for terminating optimization.
48
37
  # If solver = 'svd', this parameter is ignored.
49
38
  # @param verbose [Boolean] The flag indicating whether to output loss during iteration.
50
- # @param random_seed [Integer] The seed value using to initialize the random generator.
51
- def initialize(reg_param: 1.0, fit_bias: true, bias_scale: 1.0,
52
- max_iter: 1000, tol: 1e-4, verbose: false, random_seed: nil)
39
+ def initialize(reg_param: 1.0, fit_bias: true, bias_scale: 1.0, max_iter: 1000, tol: 1e-4, verbose: false)
53
40
  super()
54
41
  @params = {
55
42
  reg_param: reg_param,
@@ -57,10 +44,8 @@ module Rumale
57
44
  bias_scale: bias_scale,
58
45
  max_iter: max_iter,
59
46
  tol: tol,
60
- verbose: verbose,
61
- random_seed: random_seed || srand
47
+ verbose: verbose
62
48
  }
63
- @rng = Random.new(@params[:random_seed])
64
49
  end
65
50
 
66
51
  # Fit the model with given training data.
@@ -69,17 +54,16 @@ module Rumale
69
54
  # @param y [Numo::DFloat] (shape: [n_samples, n_outputs]) The target values to be used for fitting the model.
70
55
  # @return [NonneagtiveLeastSquare] The learned regressor itself.
71
56
  def fit(x, y)
72
- x = ::Rumale::Validation.check_convert_sample_array(x)
73
- y = ::Rumale::Validation.check_convert_target_value_array(y)
74
- ::Rumale::Validation.check_sample_size(x, y)
57
+ x = Rumale::Validation.check_convert_sample_array(x)
58
+ y = Rumale::Validation.check_convert_target_value_array(y)
59
+ Rumale::Validation.check_sample_size(x, y)
75
60
 
76
61
  x = expand_feature(x) if fit_bias?
77
62
 
78
63
  n_features = x.shape[1]
79
64
  n_outputs = single_target?(y) ? 1 : y.shape[1]
80
65
 
81
- w_init = ::Rumale::Utils.rand_normal([n_outputs, n_features], @rng.dup).flatten.dup
82
- w_init[w_init.lt(0)] = 0
66
+ w_init = Numo::DFloat.zeros(n_outputs * n_features)
83
67
  bounds = Numo::DFloat.zeros(n_outputs * n_features, 2)
84
68
  bounds.shape[0].times { |n| bounds[n, 1] = Float::INFINITY }
85
69
 
@@ -89,15 +73,8 @@ module Rumale
89
73
  )
90
74
 
91
75
  @n_iter = res[:n_iter]
92
- w = single_target?(y) ? res[:x] : res[:x].reshape(n_outputs, n_features).transpose
93
-
94
- if fit_bias?
95
- @weight_vec = single_target?(y) ? w[0...-1].dup : w[0...-1, true].dup
96
- @bias_term = single_target?(y) ? w[-1] : w[-1, true].dup
97
- else
98
- @weight_vec = w.dup
99
- @bias_term = single_target?(y) ? 0 : Numo::DFloat.zeros(y.shape[1])
100
- end
76
+ w = single_target?(y) ? res[:x] : res[:x].reshape(n_outputs, n_features)
77
+ @weight_vec, @bias_term = split_weight(w)
101
78
 
102
79
  self
103
80
  end
@@ -107,7 +84,7 @@ module Rumale
107
84
  # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The samples to predict the values.
108
85
  # @return [Numo::DFloat] (shape: [n_samples, n_outputs]) Predicted values per sample.
109
86
  def predict(x)
110
- x = ::Rumale::Validation.check_convert_sample_array(x)
87
+ x = Rumale::Validation.check_convert_sample_array(x)
111
88
 
112
89
  x.dot(@weight_vec.transpose) + @bias_term
113
90
  end
@@ -124,15 +101,6 @@ module Rumale
124
101
  [loss, gradient.flatten.dup]
125
102
  end
126
103
 
127
- def expand_feature(x)
128
- n_samples = x.shape[0]
129
- Numo::NArray.hstack([x, Numo::DFloat.ones([n_samples, 1]) * @params[:bias_scale]])
130
- end
131
-
132
- def fit_bias?
133
- @params[:fit_bias] == true
134
- end
135
-
136
104
  def single_target?(y)
137
105
  y.ndim == 1
138
106
  end