rumale 0.10.0 → 0.11.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.
@@ -48,13 +48,17 @@ module Rumale
48
48
  # @param min_samples_leaf [Integer] The minimum number of samples at a leaf node.
49
49
  # @param max_features [Integer] The number of features to consider when searching optimal split point.
50
50
  # If nil is given, split process considers all features.
51
+ # @param n_jobs [Integer] The number of jobs for running the fit method in parallel.
52
+ # If nil is given, the method does not execute in parallel.
53
+ # If zero or less is given, it becomes equal to the number of processors.
54
+ # This parameter is ignored if the Parallel gem is not loaded.
51
55
  # @param random_seed [Integer] The seed value using to initialize the random generator.
52
56
  # It is used to randomly determine the order of features when deciding spliting point.
53
57
  def initialize(n_estimators: 10,
54
58
  criterion: 'gini', max_depth: nil, max_leaf_nodes: nil, min_samples_leaf: 1,
55
- max_features: nil, random_seed: nil)
59
+ max_features: nil, n_jobs: nil, random_seed: nil)
56
60
  check_params_type_or_nil(Integer, max_depth: max_depth, max_leaf_nodes: max_leaf_nodes,
57
- max_features: max_features, random_seed: random_seed)
61
+ max_features: max_features, n_jobs: n_jobs, random_seed: random_seed)
58
62
  check_params_integer(n_estimators: n_estimators, min_samples_leaf: min_samples_leaf)
59
63
  check_params_string(criterion: criterion)
60
64
  check_params_positive(n_estimators: n_estimators, max_depth: max_depth,
@@ -67,6 +71,7 @@ module Rumale
67
71
  @params[:max_leaf_nodes] = max_leaf_nodes
68
72
  @params[:min_samples_leaf] = min_samples_leaf
69
73
  @params[:max_features] = max_features
74
+ @params[:n_jobs] = n_jobs
70
75
  @params[:random_seed] = random_seed
71
76
  @params[:random_seed] ||= srand
72
77
  @estimators = nil
@@ -89,19 +94,28 @@ module Rumale
89
94
  @params[:max_features] = Math.sqrt(n_features).to_i unless @params[:max_features].is_a?(Integer)
90
95
  @params[:max_features] = [[1, @params[:max_features]].max, n_features].min
91
96
  @classes = Numo::Int32.asarray(y.to_a.uniq.sort)
92
- @feature_importances = Numo::DFloat.zeros(n_features)
93
97
  # Construct forest.
94
- @estimators = Array.new(@params[:n_estimators]) do
95
- tree = Tree::DecisionTreeClassifier.new(
96
- criterion: @params[:criterion], max_depth: @params[:max_depth],
97
- max_leaf_nodes: @params[:max_leaf_nodes], min_samples_leaf: @params[:min_samples_leaf],
98
- max_features: @params[:max_features], random_seed: @rng.rand(Rumale::Values.int_max)
99
- )
100
- bootstrap_ids = Array.new(n_samples) { @rng.rand(0...n_samples) }
101
- tree.fit(x[bootstrap_ids, true], y[bootstrap_ids])
102
- @feature_importances += tree.feature_importances
103
- tree
104
- end
98
+ @estimators =
99
+ if enable_parallel?
100
+ rngs = Array.new(@params[:n_estimators]) { Random.new(@rng.rand(Rumale::Values.int_max)) }
101
+ # :nocov:
102
+ parallel_map(@params[:n_estimators]) do |n|
103
+ bootstrap_ids = Array.new(n_samples) { rngs[n].rand(0...n_samples) }
104
+ plant_tree(rngs[n].rand(Rumale::Values.int_max)).fit(x[bootstrap_ids, true], y[bootstrap_ids])
105
+ end
106
+ # :nocov:
107
+ else
108
+ Array.new(@params[:n_estimators]) do
109
+ bootstrap_ids = Array.new(n_samples) { @rng.rand(0...n_samples) }
110
+ plant_tree(@rng.rand(Rumale::Values.int_max)).fit(x[bootstrap_ids, true], y[bootstrap_ids])
111
+ end
112
+ end
113
+ @feature_importances =
114
+ if enable_parallel?
115
+ parallel_map(@params[:n_estimators]) { |n| @estimators[n].feature_importances }.reduce(&:+)
116
+ else
117
+ @estimators.map(&:feature_importances).reduce(&:+)
118
+ end
105
119
  @feature_importances /= @feature_importances.sum
106
120
  self
107
121
  end
@@ -112,18 +126,16 @@ module Rumale
112
126
  # @return [Numo::Int32] (shape: [n_samples]) Predicted class label per sample.
113
127
  def predict(x)
114
128
  check_sample_array(x)
115
- n_samples, = x.shape
116
- n_classes = @classes.size
117
- classes_arr = @classes.to_a
118
- ballot_box = Numo::DFloat.zeros(n_samples, n_classes)
119
- @estimators.each do |tree|
120
- predicted = tree.predict(x)
121
- n_samples.times do |n|
122
- class_id = classes_arr.index(predicted[n])
123
- ballot_box[n, class_id] += 1.0 unless class_id.nil?
124
- end
125
- end
126
- Numo::Int32[*Array.new(n_samples) { |n| @classes[ballot_box[n, true].max_index] }]
129
+ n_samples = x.shape[0]
130
+ n_estimators = @estimators.size
131
+ predicted = if enable_parallel?
132
+ predict_set = parallel_map(n_estimators) { |n| @estimators[n].predict(x).to_a }.transpose
133
+ parallel_map(n_samples) { |n| predict_set[n].group_by { |v| v }.max_by { |_k, v| v.size }.first }
134
+ else
135
+ predict_set = @estimators.map { |tree| tree.predict(x).to_a }.transpose
136
+ Array.new(n_samples) { |n| predict_set[n].group_by { |v| v }.max_by { |_k, v| v.size }.first }
137
+ end
138
+ Numo::Int32.asarray(predicted)
127
139
  end
128
140
 
129
141
  # Predict probability for samples.
@@ -132,18 +144,12 @@ module Rumale
132
144
  # @return [Numo::DFloat] (shape: [n_samples, n_classes]) Predicted probability of each class per sample.
133
145
  def predict_proba(x)
134
146
  check_sample_array(x)
135
- n_samples, = x.shape
136
- n_classes = @classes.size
137
- classes_arr = @classes.to_a
138
- ballot_box = Numo::DFloat.zeros(n_samples, n_classes)
139
- @estimators.each do |tree|
140
- probs = tree.predict_proba(x)
141
- tree.classes.size.times do |n|
142
- class_id = classes_arr.index(tree.classes[n])
143
- ballot_box[true, class_id] += probs[true, n] unless class_id.nil?
144
- end
147
+ n_estimators = @estimators.size
148
+ if enable_parallel?
149
+ parallel_map(n_estimators) { |n| predict_proba_tree(@estimators[n], x) }.reduce(&:+) / n_estimators
150
+ else
151
+ @estimators.map { |tree| predict_proba_tree(tree, x) }.reduce(&:+) / n_estimators
145
152
  end
146
- (ballot_box.transpose / ballot_box.sum(axis: 1)).transpose
147
153
  end
148
154
 
149
155
  # Return the index of the leaf that each sample reached.
@@ -175,6 +181,29 @@ module Rumale
175
181
  @rng = obj[:rng]
176
182
  nil
177
183
  end
184
+
185
+ private
186
+
187
+ def plant_tree(rnd_seed)
188
+ Tree::DecisionTreeClassifier.new(
189
+ criterion: @params[:criterion], max_depth: @params[:max_depth],
190
+ max_leaf_nodes: @params[:max_leaf_nodes], min_samples_leaf: @params[:min_samples_leaf],
191
+ max_features: @params[:max_features], random_seed: rnd_seed
192
+ )
193
+ end
194
+
195
+ def predict_proba_tree(tree, x)
196
+ # initialize some variables.
197
+ n_samples = x.shape[0]
198
+ base_classes = @classes.to_a
199
+ n_classes = base_classes.size
200
+ class_ids = tree.classes.map { |c| base_classes.index(c) }
201
+ # predict probabilities.
202
+ probs = Numo::DFloat.zeros(n_samples, n_classes)
203
+ tree_probs = tree.predict_proba(x)
204
+ class_ids.each_with_index { |i, j| probs[true, i] = tree_probs[true, j] }
205
+ probs
206
+ end
178
207
  end
179
208
  end
180
209
  end
@@ -43,13 +43,17 @@ module Rumale
43
43
  # @param min_samples_leaf [Integer] The minimum number of samples at a leaf node.
44
44
  # @param max_features [Integer] The number of features to consider when searching optimal split point.
45
45
  # If nil is given, split process considers all features.
46
+ # @param n_jobs [Integer] The number of jobs for running the fit and predict methods in parallel.
47
+ # If nil is given, the methods do not execute in parallel.
48
+ # If zero or less is given, it becomes equal to the number of processors.
49
+ # This parameter is ignored if the Parallel gem is not loaded.
46
50
  # @param random_seed [Integer] The seed value using to initialize the random generator.
47
51
  # It is used to randomly determine the order of features when deciding spliting point.
48
52
  def initialize(n_estimators: 10,
49
53
  criterion: 'mse', max_depth: nil, max_leaf_nodes: nil, min_samples_leaf: 1,
50
- max_features: nil, random_seed: nil)
54
+ max_features: nil, n_jobs: nil, random_seed: nil)
51
55
  check_params_type_or_nil(Integer, max_depth: max_depth, max_leaf_nodes: max_leaf_nodes,
52
- max_features: max_features, random_seed: random_seed)
56
+ max_features: max_features, n_jobs: n_jobs, random_seed: random_seed)
53
57
  check_params_integer(n_estimators: n_estimators, min_samples_leaf: min_samples_leaf)
54
58
  check_params_string(criterion: criterion)
55
59
  check_params_positive(n_estimators: n_estimators, max_depth: max_depth,
@@ -62,6 +66,7 @@ module Rumale
62
66
  @params[:max_leaf_nodes] = max_leaf_nodes
63
67
  @params[:min_samples_leaf] = min_samples_leaf
64
68
  @params[:max_features] = max_features
69
+ @params[:n_jobs] = n_jobs
65
70
  @params[:random_seed] = random_seed
66
71
  @params[:random_seed] ||= srand
67
72
  @estimators = nil
@@ -82,20 +87,31 @@ module Rumale
82
87
  n_samples, n_features = x.shape
83
88
  @params[:max_features] = Math.sqrt(n_features).to_i unless @params[:max_features].is_a?(Integer)
84
89
  @params[:max_features] = [[1, @params[:max_features]].max, n_features].min
85
- @feature_importances = Numo::DFloat.zeros(n_features)
86
90
  single_target = y.shape[1].nil?
87
91
  # Construct forest.
88
- @estimators = Array.new(@params[:n_estimators]) do
89
- tree = Tree::DecisionTreeRegressor.new(
90
- criterion: @params[:criterion], max_depth: @params[:max_depth],
91
- max_leaf_nodes: @params[:max_leaf_nodes], min_samples_leaf: @params[:min_samples_leaf],
92
- max_features: @params[:max_features], random_seed: @rng.rand(Rumale::Values.int_max)
93
- )
94
- bootstrap_ids = Array.new(n_samples) { @rng.rand(0...n_samples) }
95
- tree.fit(x[bootstrap_ids, true], single_target ? y[bootstrap_ids] : y[bootstrap_ids, true])
96
- @feature_importances += tree.feature_importances
97
- tree
98
- end
92
+ @estimators =
93
+ if enable_parallel?
94
+ rngs = Array.new(@params[:n_estimators]) { Random.new(@rng.rand(Rumale::Values.int_max)) }
95
+ # :nocov:
96
+ parallel_map(@params[:n_estimators]) do |n|
97
+ bootstrap_ids = Array.new(n_samples) { rngs[n].rand(0...n_samples) }
98
+ tree = plant_tree(rngs[n].rand(Rumale::Values.int_max))
99
+ tree.fit(x[bootstrap_ids, true], single_target ? y[bootstrap_ids] : y[bootstrap_ids, true])
100
+ end
101
+ # :nocov:
102
+ else
103
+ Array.new(@params[:n_estimators]) do
104
+ bootstrap_ids = Array.new(n_samples) { @rng.rand(0...n_samples) }
105
+ tree = plant_tree(@rng.rand(Rumale::Values.int_max))
106
+ tree.fit(x[bootstrap_ids, true], single_target ? y[bootstrap_ids] : y[bootstrap_ids, true])
107
+ end
108
+ end
109
+ @feature_importances =
110
+ if enable_parallel?
111
+ parallel_map(@params[:n_estimators]) { |n| @estimators[n].feature_importances }.reduce(&:+)
112
+ else
113
+ @estimators.map(&:feature_importances).reduce(&:+)
114
+ end
99
115
  @feature_importances /= @feature_importances.sum
100
116
  self
101
117
  end
@@ -106,7 +122,11 @@ module Rumale
106
122
  # @return [Numo::DFloat] (shape: [n_samples, n_outputs]) Predicted value per sample.
107
123
  def predict(x)
108
124
  check_sample_array(x)
109
- @estimators.map { |est| est.predict(x) }.reduce(&:+) / @params[:n_estimators]
125
+ if enable_parallel?
126
+ parallel_map(@params[:n_estimators]) { |n| @estimators[n].predict(x) }.reduce(&:+) / @params[:n_estimators]
127
+ else
128
+ @estimators.map { |tree| tree.predict(x) }.reduce(&:+) / @params[:n_estimators]
129
+ end
110
130
  end
111
131
 
112
132
  # Return the index of the leaf that each sample reached.
@@ -136,6 +156,16 @@ module Rumale
136
156
  @rng = obj[:rng]
137
157
  nil
138
158
  end
159
+
160
+ private
161
+
162
+ def plant_tree(rnd_seed)
163
+ Tree::DecisionTreeRegressor.new(
164
+ criterion: @params[:criterion], max_depth: @params[:max_depth],
165
+ max_leaf_nodes: @params[:max_leaf_nodes], min_samples_leaf: @params[:min_samples_leaf],
166
+ max_features: @params[:max_features], random_seed: rnd_seed
167
+ )
168
+ end
139
169
  end
140
170
  end
141
171
  end
@@ -42,17 +42,22 @@ module Rumale
42
42
  # @param reg_param [Float] The regularization parameter.
43
43
  # @param max_iter [Integer] The maximum number of iterations.
44
44
  # @param probability [Boolean] The flag indicating whether to perform probability estimation.
45
+ # @param n_jobs [Integer] The number of jobs for running the fit and predict methods in parallel.
46
+ # If nil is given, the methods do not execute in parallel.
47
+ # If zero or less is given, it becomes equal to the number of processors.
48
+ # This parameter is ignored if the Parallel gem is not loaded.
45
49
  # @param random_seed [Integer] The seed value using to initialize the random generator.
46
- def initialize(reg_param: 1.0, max_iter: 1000, probability: false, random_seed: nil)
50
+ def initialize(reg_param: 1.0, max_iter: 1000, probability: false, n_jobs: nil, random_seed: nil)
47
51
  check_params_float(reg_param: reg_param)
48
52
  check_params_integer(max_iter: max_iter)
49
53
  check_params_boolean(probability: probability)
50
- check_params_type_or_nil(Integer, random_seed: random_seed)
54
+ check_params_type_or_nil(Integer, n_jobs: n_jobs, random_seed: random_seed)
51
55
  check_params_positive(reg_param: reg_param, max_iter: max_iter)
52
56
  @params = {}
53
57
  @params[:reg_param] = reg_param
54
58
  @params[:max_iter] = max_iter
55
59
  @params[:probability] = probability
60
+ @params[:n_jobs] = n_jobs
56
61
  @params[:random_seed] = random_seed
57
62
  @params[:random_seed] ||= srand
58
63
  @weight_vec = nil
@@ -79,14 +84,30 @@ module Rumale
79
84
  if n_classes > 2
80
85
  @weight_vec = Numo::DFloat.zeros(n_classes, n_features)
81
86
  @prob_param = Numo::DFloat.zeros(n_classes, 2)
82
- n_classes.times do |n|
83
- bin_y = Numo::Int32.cast(y.eq(@classes[n])) * 2 - 1
84
- @weight_vec[n, true] = binary_fit(x, bin_y)
85
- @prob_param[n, true] = if @params[:probability]
86
- Rumale::ProbabilisticOutput.fit_sigmoid(x.dot(@weight_vec[n, true].transpose), bin_y)
87
- else
88
- Numo::DFloat[1, 0]
89
- end
87
+ if enable_parallel?
88
+ # :nocov:
89
+ models = parallel_map(n_classes) do |n|
90
+ bin_y = Numo::Int32.cast(y.eq(@classes[n])) * 2 - 1
91
+ w = binary_fit(x, bin_y)
92
+ p = if @params[:probability]
93
+ Rumale::ProbabilisticOutput.fit_sigmoid(x.dot(w), bin_y)
94
+ else
95
+ Numo::DFloat[1, 0]
96
+ end
97
+ [w, p]
98
+ end
99
+ # :nocov:
100
+ n_classes.times { |n| @weight_vec[n, true], @prob_param[n, true] = models[n] }
101
+ else
102
+ n_classes.times do |n|
103
+ bin_y = Numo::Int32.cast(y.eq(@classes[n])) * 2 - 1
104
+ @weight_vec[n, true] = binary_fit(x, bin_y)
105
+ @prob_param[n, true] = if @params[:probability]
106
+ Rumale::ProbabilisticOutput.fit_sigmoid(x.dot(@weight_vec[n, true].transpose), bin_y)
107
+ else
108
+ Numo::DFloat[1, 0]
109
+ end
110
+ end
90
111
  end
91
112
  else
92
113
  negative_label = y.to_a.uniq.min
@@ -125,7 +146,12 @@ module Rumale
125
146
 
126
147
  n_samples, = x.shape
127
148
  decision_values = decision_function(x)
128
- Numo::Int32.asarray(Array.new(n_samples) { |n| @classes[decision_values[n, true].max_index] })
149
+ predicted = if enable_parallel?
150
+ parallel_map(n_samples) { |n| @classes[decision_values[n, true].max_index] }
151
+ else
152
+ Array.new(n_samples) { |n| @classes[decision_values[n, true].max_index] }
153
+ end
154
+ Numo::Int32.asarray(predicted)
129
155
  end
130
156
 
131
157
  # Predict probability for samples.
@@ -20,9 +20,12 @@ module Rumale
20
20
  # @param batch_size [Integer] The size of the mini batches.
21
21
  # @param optimizer [Optimizer] The optimizer to calculate adaptive learning rate.
22
22
  # If nil is given, Nadam is used.
23
+ # @param n_jobs [Integer] The number of jobs for running the fit and predict methods in parallel.
24
+ # If nil is given, the methods do not execute in parallel.
25
+ # If zero or less is given, it becomes equal to the number of processors.
23
26
  # @param random_seed [Integer] The seed value using to initialize the random generator.
24
27
  def initialize(reg_param: 1.0, fit_bias: false, bias_scale: 1.0,
25
- max_iter: 1000, batch_size: 10, optimizer: nil, random_seed: nil)
28
+ max_iter: 1000, batch_size: 10, optimizer: nil, n_jobs: nil, random_seed: nil)
26
29
  @params = {}
27
30
  @params[:reg_param] = reg_param
28
31
  @params[:fit_bias] = fit_bias
@@ -31,6 +34,7 @@ module Rumale
31
34
  @params[:batch_size] = batch_size
32
35
  @params[:optimizer] = optimizer
33
36
  @params[:optimizer] ||= Optimizer::Nadam.new
37
+ @params[:n_jobs] = n_jobs
34
38
  @params[:random_seed] = random_seed
35
39
  @params[:random_seed] ||= srand
36
40
  @weight_vec = nil
@@ -41,12 +41,17 @@ module Rumale
41
41
  # @param batch_size [Integer] The size of the mini batches.
42
42
  # @param optimizer [Optimizer] The optimizer to calculate adaptive learning rate.
43
43
  # If nil is given, Nadam is used.
44
+ # @param n_jobs [Integer] The number of jobs for running the fit method in parallel.
45
+ # If nil is given, the method does not execute in parallel.
46
+ # If zero or less is given, it becomes equal to the number of processors.
47
+ # This parameter is ignored if the Parallel gem is not loaded.
44
48
  # @param random_seed [Integer] The seed value using to initialize the random generator.
45
- def initialize(reg_param: 1.0, fit_bias: false, bias_scale: 1.0, max_iter: 1000, batch_size: 10, optimizer: nil, random_seed: nil)
49
+ def initialize(reg_param: 1.0, fit_bias: false, bias_scale: 1.0, max_iter: 1000, batch_size: 10, optimizer: nil,
50
+ n_jobs: nil, random_seed: nil)
46
51
  check_params_float(reg_param: reg_param, bias_scale: bias_scale)
47
52
  check_params_integer(max_iter: max_iter, batch_size: batch_size)
48
53
  check_params_boolean(fit_bias: fit_bias)
49
- check_params_type_or_nil(Integer, random_seed: random_seed)
54
+ check_params_type_or_nil(Integer, n_jobs: n_jobs, random_seed: random_seed)
50
55
  check_params_positive(reg_param: reg_param, max_iter: max_iter, batch_size: batch_size)
51
56
  super
52
57
  end
@@ -67,11 +72,15 @@ module Rumale
67
72
  if n_outputs > 1
68
73
  @weight_vec = Numo::DFloat.zeros(n_outputs, n_features)
69
74
  @bias_term = Numo::DFloat.zeros(n_outputs)
70
- n_outputs.times { |n| @weight_vec[n, true], @bias_term[n] = partial_fit(x, y[true, n]) }
75
+ if enable_parallel?
76
+ models = parallel_map(n_outputs) { |n| partial_fit(x, y[true, n]) }
77
+ n_outputs.times { |n| @weight_vec[n, true], @bias_term[n] = models[n] }
78
+ else
79
+ n_outputs.times { |n| @weight_vec[n, true], @bias_term[n] = partial_fit(x, y[true, n]) }
80
+ end
71
81
  else
72
82
  @weight_vec, @bias_term = partial_fit(x, y)
73
83
  end
74
-
75
84
  self
76
85
  end
77
86
 
@@ -37,12 +37,17 @@ module Rumale
37
37
  # @param batch_size [Integer] The size of the mini batches.
38
38
  # @param optimizer [Optimizer] The optimizer to calculate adaptive learning rate.
39
39
  # If nil is given, Nadam is used.
40
+ # @param n_jobs [Integer] The number of jobs for running the fit method in parallel.
41
+ # If nil is given, the method does not execute in parallel.
42
+ # If zero or less is given, it becomes equal to the number of processors.
43
+ # This parameter is ignored if the Parallel gem is not loaded.
40
44
  # @param random_seed [Integer] The seed value using to initialize the random generator.
41
- def initialize(fit_bias: false, bias_scale: 1.0, max_iter: 1000, batch_size: 10, optimizer: nil, random_seed: nil)
45
+ def initialize(fit_bias: false, bias_scale: 1.0, max_iter: 1000, batch_size: 10, optimizer: nil,
46
+ n_jobs: nil, random_seed: nil)
42
47
  check_params_float(bias_scale: bias_scale)
43
48
  check_params_integer(max_iter: max_iter, batch_size: batch_size)
44
49
  check_params_boolean(fit_bias: fit_bias)
45
- check_params_type_or_nil(Integer, random_seed: random_seed)
50
+ check_params_type_or_nil(Integer, n_jobs: n_jobs, random_seed: random_seed)
46
51
  check_params_positive(max_iter: max_iter, batch_size: batch_size)
47
52
  keywd_args = method(:initialize).parameters.map { |_t, arg| [arg, binding.local_variable_get(arg)] }.to_h.merge(reg_param: 0.0)
48
53
  super(keywd_args)
@@ -64,7 +69,12 @@ module Rumale
64
69
  if n_outputs > 1
65
70
  @weight_vec = Numo::DFloat.zeros(n_outputs, n_features)
66
71
  @bias_term = Numo::DFloat.zeros(n_outputs)
67
- n_outputs.times { |n| @weight_vec[n, true], @bias_term[n] = partial_fit(x, y[true, n]) }
72
+ if enable_parallel?
73
+ models = parallel_map(n_outputs) { |n| partial_fit(x, y[true, n]) }
74
+ n_outputs.times { |n| @weight_vec[n, true], @bias_term[n] = models[n] }
75
+ else
76
+ n_outputs.times { |n| @weight_vec[n, true], @bias_term[n] = partial_fit(x, y[true, n]) }
77
+ end
68
78
  else
69
79
  @weight_vec, @bias_term = partial_fit(x, y)
70
80
  end
@@ -46,13 +46,17 @@ module Rumale
46
46
  # @param batch_size [Integer] The size of the mini batches.
47
47
  # @param optimizer [Optimizer] The optimizer to calculate adaptive learning rate.
48
48
  # If nil is given, Nadam is used.
49
+ # @param n_jobs [Integer] The number of jobs for running the fit and predict methods in parallel.
50
+ # If nil is given, the methods do not execute in parallel.
51
+ # If zero or less is given, it becomes equal to the number of processors.
52
+ # This parameter is ignored if the Parallel gem is not loaded.
49
53
  # @param random_seed [Integer] The seed value using to initialize the random generator.
50
54
  def initialize(reg_param: 1.0, fit_bias: false, bias_scale: 1.0,
51
- max_iter: 1000, batch_size: 20, optimizer: nil, random_seed: nil)
55
+ max_iter: 1000, batch_size: 20, optimizer: nil, n_jobs: nil, random_seed: nil)
52
56
  check_params_float(reg_param: reg_param, bias_scale: bias_scale)
53
57
  check_params_integer(max_iter: max_iter, batch_size: batch_size)
54
58
  check_params_boolean(fit_bias: fit_bias)
55
- check_params_type_or_nil(Integer, random_seed: random_seed)
59
+ check_params_type_or_nil(Integer, n_jobs: n_jobs, random_seed: random_seed)
56
60
  check_params_positive(reg_param: reg_param, bias_scale: bias_scale, max_iter: max_iter, batch_size: batch_size)
57
61
  super
58
62
  @classes = nil
@@ -75,9 +79,19 @@ module Rumale
75
79
  if n_classes > 2
76
80
  @weight_vec = Numo::DFloat.zeros(n_classes, n_features)
77
81
  @bias_term = Numo::DFloat.zeros(n_classes)
78
- n_classes.times do |n|
79
- bin_y = Numo::Int32.cast(y.eq(@classes[n])) * 2 - 1
80
- @weight_vec[n, true], @bias_term[n] = partial_fit(x, bin_y)
82
+ if enable_parallel?
83
+ # :nocov:
84
+ models = parallel_map(n_classes) do |n|
85
+ bin_y = Numo::Int32.cast(y.eq(@classes[n])) * 2 - 1
86
+ partial_fit(x, bin_y)
87
+ end
88
+ # :nocov:
89
+ n_classes.times { |n| @weight_vec[n, true], @bias_term[n] = models[n] }
90
+ else
91
+ n_classes.times do |n|
92
+ bin_y = Numo::Int32.cast(y.eq(@classes[n])) * 2 - 1
93
+ @weight_vec[n, true], @bias_term[n] = partial_fit(x, bin_y)
94
+ end
81
95
  end
82
96
  else
83
97
  negative_label = y.to_a.uniq.min
@@ -108,7 +122,12 @@ module Rumale
108
122
 
109
123
  n_samples, = x.shape
110
124
  decision_values = predict_proba(x)
111
- Numo::Int32.asarray(Array.new(n_samples) { |n| @classes[decision_values[n, true].max_index] })
125
+ predicted = if enable_parallel?
126
+ parallel_map(n_samples) { |n| @classes[decision_values[n, true].max_index] }
127
+ else
128
+ Array.new(n_samples) { |n| @classes[decision_values[n, true].max_index] }
129
+ end
130
+ Numo::Int32.asarray(predicted)
112
131
  end
113
132
 
114
133
  # Predict probability for samples.