lightgbm 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49e0eef0a10a444e0cc24c8188268d349037fb12054b3f3f73ab14ed54fae3d7
4
- data.tar.gz: 9ee78189ec31bfb3dc9cea6fe5836f97a010097cc821819d05236a899d0654af
3
+ metadata.gz: 723130d41ea9196bcbd7bcffeb865c40c65985f26eca018d49bd176d33c43142
4
+ data.tar.gz: d92b41899ff72da2ef4e5782bf4d2840caee1554107d9fd5d02bd6728829585a
5
5
  SHA512:
6
- metadata.gz: d95050754e85ee004df08c4761f31f1bfc97e3efbcd3ea0ae2251f5a84eeff2978e16118411ddeced74a9c7d3fd731176488cbbac4ed2bdd840e55e4dd6172db
7
- data.tar.gz: 52dcca52827fffca3d638c814eec359c4f2d397b025cc9ee99323bebbbf5436d3a7fcf569390f0c71eb9623c68a711b0e32ba38a53364873fe9d0de99b2f3f66
6
+ metadata.gz: 6960dbf1e2a884705e8a2752952392483c0ab1e74e970382b45df747ccbedbc0d978d3910e2b935190c98a9c45315841b53e27128aa5944e3a7834808e05582a
7
+ data.tar.gz: c6e793933dc794fa62099580ad35f29d7e5e3ae24a07df4335dca3d68571bc1d5b360a7cf0859d75d245a77d470a0dafba0cb5d86edda1974f3bc532b0f5c11a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.1.2
2
+
3
+ - Added `cv` method
4
+ - Added early stopping
5
+ - Fixed multiclass classification
6
+
1
7
  ## 0.1.1
2
8
 
3
9
  - Added training API
data/README.md CHANGED
@@ -16,6 +16,8 @@ Add this line to your application’s Gemfile:
16
16
  gem 'lightgbm'
17
17
  ```
18
18
 
19
+ ## Getting Started
20
+
19
21
  Train a model
20
22
 
21
23
  ```ruby
@@ -30,13 +32,13 @@ Predict
30
32
  booster.predict(x_test)
31
33
  ```
32
34
 
33
- Save the model
35
+ Save the model to a file
34
36
 
35
37
  ```ruby
36
38
  booster.save_model("model.txt")
37
39
  ```
38
40
 
39
- Load a model from a file
41
+ Load the model from a file
40
42
 
41
43
  ```ruby
42
44
  booster = LightGBM::Booster.new(model_file: "model.txt")
@@ -48,30 +50,32 @@ Get feature importance
48
50
  booster.feature_importance
49
51
  ```
50
52
 
51
- ## Reference
52
-
53
- ### Booster
53
+ ## Early Stopping
54
54
 
55
55
  ```ruby
56
- booster = LightGBM::Booster.new(model_str: "tree...")
57
- booster.to_json
58
- booster.model_to_string
59
- booster.current_iteration
56
+ LightGBM.train(params, train_set, valid_set: [train_set, test_set], early_stopping_rounds: 5)
60
57
  ```
61
58
 
62
- ### Dataset
59
+ ## CV
63
60
 
64
61
  ```ruby
65
- dataset = LightGBM::Dataset.new(data, label: label, weight: weight, params: params)
66
- dataset.num_data
67
- dataset.num_feature
68
-
69
- # note: only works with unquoted CSVs
70
- dataset = LightGBM::Dataset.new("data.csv", params: {headers: true, label: "name:label"})
71
- dataset.save_binary("train.bin")
72
- dataset.dump_text("train.txt")
62
+ LightGBM.cv(params, train_set, nfold: 5, verbose_eval: true)
73
63
  ```
74
64
 
65
+ ## Reference
66
+
67
+ This library follows the [Data Structure and Training APIs](https://lightgbm.readthedocs.io/en/latest/Python-API.html) for the Python library. A few differences are:
68
+
69
+ - The default verbosity is `-1`
70
+ - With the `cv` method, `stratified` is set to `false`
71
+
72
+ Some methods and options are also missing at the moment. PRs welcome!
73
+
74
+ ## Helpful Resources
75
+
76
+ - [Parameters](https://lightgbm.readthedocs.io/en/latest/Parameters.html)
77
+ - [Parameter Tuning](https://lightgbm.readthedocs.io/en/latest/Parameters-Tuning.html)
78
+
75
79
  ## Credits
76
80
 
77
81
  Thanks to the [xgboost](https://github.com/PairOnAir/xgboost-ruby) gem for serving as an initial reference, and Selva Prabhakaran for the [test datasets](https://github.com/selva86/datasets).
data/lib/lightgbm.rb CHANGED
@@ -11,14 +11,174 @@ require "lightgbm/version"
11
11
  module LightGBM
12
12
  class Error < StandardError; end
13
13
 
14
- def self.train(params, train_set, num_boost_round: 100, valid_sets: [], valid_names: [])
15
- booster = Booster.new(params: params, train_set: train_set)
16
- valid_sets.zip(valid_names) do |data, name|
17
- booster.add_valid(data, name)
14
+ class << self
15
+ def train(params, train_set,num_boost_round: 100, valid_sets: [], valid_names: [], early_stopping_rounds: nil, verbose_eval: true)
16
+ booster = Booster.new(params: params, train_set: train_set)
17
+
18
+ valid_contain_train = false
19
+ valid_sets.zip(valid_names).each_with_index do |(data, name), i|
20
+ if data == train_set
21
+ booster.train_data_name = name || "training"
22
+ valid_contain_train = true
23
+ else
24
+ booster.add_valid(data, name || "valid_#{i}")
25
+ end
26
+ end
27
+
28
+ booster.best_iteration = 0
29
+
30
+ if early_stopping_rounds
31
+ best_score = []
32
+ best_iter = []
33
+ best_message = []
34
+
35
+ puts "Training until validation scores don't improve for #{early_stopping_rounds.to_i} rounds." if verbose_eval
36
+ end
37
+
38
+ num_boost_round.times do |iteration|
39
+ booster.update
40
+
41
+ if valid_sets.any?
42
+ # print results
43
+ messages = []
44
+
45
+ if valid_contain_train
46
+ # not sure why reversed in output
47
+ booster.eval_train.reverse.each do |res|
48
+ messages << "%s's %s: %g" % [res[0], res[1], res[2]]
49
+ end
50
+ end
51
+
52
+ eval_valid = booster.eval_valid
53
+ # not sure why reversed in output
54
+ eval_valid.reverse.each do |res|
55
+ messages << "%s's %s: %g" % [res[0], res[1], res[2]]
56
+ end
57
+
58
+ message = "[#{iteration + 1}]\t#{messages.join("\t")}"
59
+
60
+ puts message if verbose_eval
61
+
62
+ if early_stopping_rounds
63
+ stop_early = false
64
+ eval_valid.each_with_index do |(_, _, score, higher_better), i|
65
+ op = higher_better ? :> : :<
66
+ if best_score[i].nil? || score.send(op, best_score[i])
67
+ best_score[i] = score
68
+ best_iter[i] = iteration
69
+ best_message[i] = message
70
+ elsif iteration - best_iter[i] >= early_stopping_rounds
71
+ booster.best_iteration = best_iter[i] + 1
72
+ puts "Early stopping, best iteration is:\n#{best_message[i]}" if verbose_eval
73
+ stop_early = true
74
+ break
75
+ end
76
+ end
77
+
78
+ break if stop_early
79
+
80
+ if iteration == num_boost_round - 1
81
+ booster.best_iteration = best_iter[0] + 1
82
+ puts "Did not meet early stopping. Best iteration is: #{best_message[0]}" if verbose_eval
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ booster
89
+ end
90
+
91
+ def cv(params, train_set, num_boost_round: 100, nfold: 5, seed: 0, shuffle: true, early_stopping_rounds: nil, verbose_eval: nil, show_stdv: true)
92
+ rand_idx = (0...train_set.num_data).to_a
93
+ rand_idx.shuffle!(random: Random.new(seed)) if shuffle
94
+
95
+ kstep = rand_idx.size / nfold
96
+ test_id = rand_idx.each_slice(kstep).to_a[0...nfold]
97
+ train_id = []
98
+ nfold.times do |i|
99
+ idx = test_id.dup
100
+ idx.delete_at(i)
101
+ train_id << idx.flatten
102
+ end
103
+
104
+ boosters = []
105
+ folds = train_id.zip(test_id)
106
+ folds.each do |(train_idx, test_idx)|
107
+ fold_train_set = train_set.subset(train_idx)
108
+ fold_valid_set = train_set.subset(test_idx)
109
+ booster = Booster.new(params: params, train_set: fold_train_set)
110
+ booster.add_valid(fold_valid_set, "valid")
111
+ boosters << booster
112
+ end
113
+
114
+ eval_hist = {}
115
+
116
+ if early_stopping_rounds
117
+ best_score = {}
118
+ best_iter = {}
119
+ end
120
+
121
+ num_boost_round.times do |iteration|
122
+ boosters.each(&:update)
123
+
124
+ scores = {}
125
+ boosters.map(&:eval_valid).map(&:reverse).flatten(1).each do |r|
126
+ (scores[r[1]] ||= []) << r[2]
127
+ end
128
+
129
+ message_parts = ["[#{iteration + 1}]"]
130
+
131
+ means = {}
132
+ scores.each do |eval_name, vals|
133
+ mean = mean(vals)
134
+ stdev = stdev(vals)
135
+
136
+ (eval_hist["#{eval_name}-mean"] ||= []) << mean
137
+ (eval_hist["#{eval_name}-stdv"] ||= []) << stdev
138
+
139
+ means[eval_name] = mean
140
+
141
+ if show_stdv
142
+ message_parts << "cv_agg's %s: %g + %g" % [eval_name, mean, stdev]
143
+ else
144
+ message_parts << "cv_agg's %s: %g" % [eval_name, mean]
145
+ end
146
+ end
147
+
148
+ puts message_parts.join("\t") if verbose_eval
149
+
150
+ if early_stopping_rounds
151
+ stop_early = false
152
+ means.each do |k, score|
153
+ if best_score[k].nil? || score < best_score[k]
154
+ best_score[k] = score
155
+ best_iter[k] = iteration
156
+ elsif iteration - best_iter[k] >= early_stopping_rounds
157
+ stop_early = true
158
+ break
159
+ end
160
+ end
161
+ break if stop_early
162
+ end
163
+ end
164
+
165
+ eval_hist
18
166
  end
19
- num_boost_round.times do
20
- booster.update
167
+
168
+ private
169
+
170
+ def mean(arr)
171
+ arr.sum / arr.size.to_f
172
+ end
173
+
174
+ # don't subtract one from arr.size
175
+ def stdev(arr)
176
+ m = mean(arr)
177
+ sum = 0
178
+ arr.each do |v|
179
+ sum += (v - m) ** 2
180
+ end
181
+ Math.sqrt(sum / arr.size)
21
182
  end
22
- booster
23
183
  end
24
184
  end
@@ -1,57 +1,65 @@
1
1
  module LightGBM
2
2
  class Booster
3
+ attr_accessor :best_iteration, :train_data_name
4
+
3
5
  def initialize(params: nil, train_set: nil, model_file: nil, model_str: nil)
4
6
  @handle = ::FFI::MemoryPointer.new(:pointer)
5
7
  if model_str
6
- out_num_iterations = ::FFI::MemoryPointer.new(:int)
7
- check_result FFI.LGBM_BoosterLoadModelFromString(model_str, out_num_iterations, @handle)
8
+ model_from_string(model_str)
8
9
  elsif model_file
9
10
  out_num_iterations = ::FFI::MemoryPointer.new(:int)
10
11
  check_result FFI.LGBM_BoosterCreateFromModelfile(model_file, out_num_iterations, @handle)
11
12
  else
13
+ params ||= {}
14
+ set_verbosity(params)
12
15
  check_result FFI.LGBM_BoosterCreate(train_set.handle_pointer, params_str(params), @handle)
13
16
  end
14
17
  # causes "Stack consistency error"
15
18
  # ObjectSpace.define_finalizer(self, self.class.finalize(handle_pointer))
19
+
20
+ self.best_iteration = -1
21
+
22
+ # TODO get names when loaded from file
23
+ @name_valid_sets = []
16
24
  end
17
25
 
18
26
  def self.finalize(pointer)
19
27
  -> { FFI.LGBM_BoosterFree(pointer) }
20
28
  end
21
29
 
22
- # TODO handle name
23
30
  def add_valid(data, name)
24
31
  check_result FFI.LGBM_BoosterAddValidData(handle_pointer, data.handle_pointer)
32
+ @name_valid_sets << name
25
33
  self # consistent with Python API
26
34
  end
27
35
 
28
- def predict(input)
29
- raise TypeError unless input.is_a?(Array)
30
-
31
- singular = input.first.is_a?(Array)
32
- input = [input] unless singular
33
-
34
- data = ::FFI::MemoryPointer.new(:float, input.count * input.first.count)
35
- data.put_array_of_float(0, input.flatten)
36
+ def current_iteration
37
+ out = ::FFI::MemoryPointer.new(:int)
38
+ check_result FFI::LGBM_BoosterGetCurrentIteration(handle_pointer, out)
39
+ out.read_int
40
+ end
36
41
 
42
+ def dump_model(num_iteration: nil, start_iteration: 0)
43
+ num_iteration ||= best_iteration
44
+ buffer_len = 1 << 20
37
45
  out_len = ::FFI::MemoryPointer.new(:int64)
38
- out_result = ::FFI::MemoryPointer.new(:double, input.count)
39
- parameter = ""
40
- check_result FFI.LGBM_BoosterPredictForMat(handle_pointer, data, 0, input.count, input.first.count, 1, 0, 0, parameter, out_len, out_result)
41
- out = out_result.read_array_of_double(out_len.read_int64)
42
-
43
- singular ? out : out.first
46
+ out_str = ::FFI::MemoryPointer.new(:string, buffer_len)
47
+ check_result FFI.LGBM_BoosterDumpModel(handle_pointer, start_iteration, num_iteration, buffer_len, out_len, out_str)
48
+ actual_len = out_len.read_int64
49
+ if actual_len > buffer_len
50
+ out_str = ::FFI::MemoryPointer.new(:string, actual_len)
51
+ check_result FFI.LGBM_BoosterDumpModel(handle_pointer, start_iteration, num_iteration, actual_len, out_len, out_str)
52
+ end
53
+ out_str.read_string
44
54
  end
55
+ alias_method :to_json, :dump_model
45
56
 
46
- def save_model(filename)
47
- check_result FFI.LGBM_BoosterSaveModel(handle_pointer, 0, 0, filename)
48
- self # consistent with Python API
57
+ def eval_valid
58
+ @name_valid_sets.each_with_index.map { |n, i| inner_eval(n, i + 1) }.flatten(1)
49
59
  end
50
60
 
51
- def update
52
- finished = ::FFI::MemoryPointer.new(:int)
53
- check_result FFI.LGBM_BoosterUpdateOneIter(handle_pointer, finished)
54
- finished.read_int == 1
61
+ def eval_train
62
+ inner_eval(train_data_name, 0)
55
63
  end
56
64
 
57
65
  def feature_importance(iteration: nil, importance_type: "split")
@@ -66,27 +74,16 @@ module LightGBM
66
74
  -1
67
75
  end
68
76
 
69
- num_features = self.num_features
70
- out_result = ::FFI::MemoryPointer.new(:double, num_features)
77
+ num_feature = self.num_feature
78
+ out_result = ::FFI::MemoryPointer.new(:double, num_feature)
71
79
  check_result FFI.LGBM_BoosterFeatureImportance(handle_pointer, iteration, importance_type, out_result)
72
- out_result.read_array_of_double(num_features)
73
- end
74
-
75
- def num_features
76
- out = ::FFI::MemoryPointer.new(:int)
77
- check_result FFI.LGBM_BoosterGetNumFeature(handle_pointer, out)
78
- out.read_int
79
- end
80
-
81
- def current_iteration
82
- out = ::FFI::MemoryPointer.new(:int)
83
- check_result FFI::LGBM_BoosterGetCurrentIteration(handle_pointer, out)
84
- out.read_int
80
+ out_result.read_array_of_double(num_feature)
85
81
  end
86
82
 
87
- # TODO fix
88
- def best_iteration
89
- -1
83
+ def model_from_string(model_str)
84
+ out_num_iterations = ::FFI::MemoryPointer.new(:int)
85
+ check_result FFI.LGBM_BoosterLoadModelFromString(model_str, out_num_iterations, @handle)
86
+ self
90
87
  end
91
88
 
92
89
  def model_to_string(num_iteration: nil, start_iteration: 0)
@@ -103,18 +100,57 @@ module LightGBM
103
100
  out_str.read_string
104
101
  end
105
102
 
106
- def to_json(num_iteration: nil, start_iteration: 0)
103
+ def num_feature
104
+ out = ::FFI::MemoryPointer.new(:int)
105
+ check_result FFI.LGBM_BoosterGetNumFeature(handle_pointer, out)
106
+ out.read_int
107
+ end
108
+ alias_method :num_features, :num_feature # legacy typo
109
+
110
+ def num_model_per_iteration
111
+ out = ::FFI::MemoryPointer.new(:int)
112
+ check_result FFI::LGBM_BoosterNumModelPerIteration(handle_pointer, out)
113
+ out.read_int
114
+ end
115
+
116
+ def num_trees
117
+ out = ::FFI::MemoryPointer.new(:int)
118
+ check_result FFI::LGBM_BoosterNumberOfTotalModel(handle_pointer, out)
119
+ out.read_int
120
+ end
121
+
122
+ # TODO support different prediction types
123
+ def predict(input, num_iteration: nil, **params)
124
+ raise TypeError unless input.is_a?(Array)
125
+
126
+ singular = !input.first.is_a?(Array)
127
+ input = [input] if singular
128
+
107
129
  num_iteration ||= best_iteration
108
- buffer_len = 1 << 20
130
+ num_class ||= num_class()
131
+
132
+ data = ::FFI::MemoryPointer.new(:float, input.count * input.first.count)
133
+ data.put_array_of_float(0, input.flatten)
134
+
109
135
  out_len = ::FFI::MemoryPointer.new(:int64)
110
- out_str = ::FFI::MemoryPointer.new(:string, buffer_len)
111
- check_result FFI.LGBM_BoosterDumpModel(handle_pointer, start_iteration, num_iteration, buffer_len, out_len, out_str)
112
- actual_len = out_len.read_int64
113
- if actual_len > buffer_len
114
- out_str = ::FFI::MemoryPointer.new(:string, actual_len)
115
- check_result FFI.LGBM_BoosterDumpModel(handle_pointer, start_iteration, num_iteration, actual_len, out_len, out_str)
116
- end
117
- out_str.read_string
136
+ out_result = ::FFI::MemoryPointer.new(:double, num_class * input.count)
137
+ check_result FFI.LGBM_BoosterPredictForMat(handle_pointer, data, 0, input.count, input.first.count, 1, 0, num_iteration, params_str(params), out_len, out_result)
138
+ out = out_result.read_array_of_double(out_len.read_int64)
139
+ out = out.each_slice(num_class).to_a if num_class > 1
140
+
141
+ singular ? out.first : out
142
+ end
143
+
144
+ def save_model(filename, num_iteration: nil, start_iteration: 0)
145
+ num_iteration ||= best_iteration
146
+ check_result FFI.LGBM_BoosterSaveModel(handle_pointer, start_iteration, num_iteration, filename)
147
+ self # consistent with Python API
148
+ end
149
+
150
+ def update
151
+ finished = ::FFI::MemoryPointer.new(:int)
152
+ check_result FFI.LGBM_BoosterUpdateOneIter(handle_pointer, finished)
153
+ finished.read_int == 1
118
154
  end
119
155
 
120
156
  private
@@ -123,6 +159,42 @@ module LightGBM
123
159
  @handle.read_pointer
124
160
  end
125
161
 
162
+ def eval_counts
163
+ out = ::FFI::MemoryPointer.new(:int)
164
+ check_result FFI::LGBM_BoosterGetEvalCounts(handle_pointer, out)
165
+ out.read_int
166
+ end
167
+
168
+ def eval_names
169
+ eval_counts ||= eval_counts()
170
+ out_len = ::FFI::MemoryPointer.new(:int)
171
+ out_strs = ::FFI::MemoryPointer.new(:pointer, eval_counts)
172
+ str_ptrs = eval_counts.times.map { ::FFI::MemoryPointer.new(:string, 255) }
173
+ out_strs.put_array_of_pointer(0, str_ptrs)
174
+ check_result FFI.LGBM_BoosterGetEvalNames(handle_pointer, out_len, out_strs)
175
+ str_ptrs.map(&:read_string)
176
+ end
177
+
178
+ def inner_eval(name, i)
179
+ eval_names ||= eval_names()
180
+
181
+ out_len = ::FFI::MemoryPointer.new(:int)
182
+ out_results = ::FFI::MemoryPointer.new(:double, eval_names.count)
183
+ check_result FFI.LGBM_BoosterGetEval(handle_pointer, i, out_len, out_results)
184
+ vals = out_results.read_array_of_double(out_len.read_int)
185
+
186
+ eval_names.zip(vals).map do |eval_name, val|
187
+ higher_better = ["auc", "ndcg@", "map@"].any? { |v| eval_name.start_with?(v) }
188
+ [name, eval_name, val, higher_better]
189
+ end
190
+ end
191
+
192
+ def num_class
193
+ out = ::FFI::MemoryPointer.new(:int)
194
+ check_result FFI::LGBM_BoosterGetNumClasses(handle_pointer, out)
195
+ out.read_int
196
+ end
197
+
126
198
  include Utils
127
199
  end
128
200
  end
@@ -2,16 +2,27 @@ module LightGBM
2
2
  class Dataset
3
3
  attr_reader :data, :params
4
4
 
5
- def initialize(data, label: nil, weight: nil, params: nil)
5
+ def initialize(data, label: nil, weight: nil, params: nil, reference: nil, used_indices: nil, categorical_feature: "auto")
6
6
  @data = data
7
7
 
8
+ # TODO stringify params
9
+ params ||= {}
10
+ params["categorical_feature"] ||= categorical_feature.join(",") if categorical_feature != "auto"
11
+ set_verbosity(params)
12
+
8
13
  @handle = ::FFI::MemoryPointer.new(:pointer)
14
+ parameters = params_str(params)
15
+ reference = reference.handle_pointer if reference
9
16
  if data.is_a?(String)
10
- check_result FFI.LGBM_DatasetCreateFromFile(data, params_str(params), nil, @handle)
17
+ check_result FFI.LGBM_DatasetCreateFromFile(data, parameters, reference, @handle)
18
+ elsif used_indices
19
+ used_row_indices = ::FFI::MemoryPointer.new(:int32, used_indices.count)
20
+ used_row_indices.put_array_of_int32(0, used_indices)
21
+ check_result FFI.LGBM_DatasetGetSubset(reference, used_row_indices, used_indices.count, parameters, @handle)
11
22
  else
12
23
  c_data = ::FFI::MemoryPointer.new(:float, data.count * data.first.count)
13
24
  c_data.put_array_of_float(0, data.flatten)
14
- check_result FFI.LGBM_DatasetCreateFromMat(c_data, 0, data.count, data.first.count, 1, params_str(params), nil, @handle)
25
+ check_result FFI.LGBM_DatasetCreateFromMat(c_data, 0, data.count, data.first.count, 1, parameters, reference, @handle)
15
26
  end
16
27
  # causes "Stack consistency error"
17
28
  # ObjectSpace.define_finalizer(self, self.class.finalize(handle_pointer))
@@ -48,6 +59,16 @@ module LightGBM
48
59
  check_result FFI.LGBM_DatasetDumpText(handle_pointer, filename)
49
60
  end
50
61
 
62
+ def subset(used_indices, params: nil)
63
+ # categorical_feature passed via params
64
+ params ||= self.params
65
+ Dataset.new(nil,
66
+ params: params,
67
+ reference: self,
68
+ used_indices: used_indices
69
+ )
70
+ end
71
+
51
72
  def self.finalize(pointer)
52
73
  -> { FFI.LGBM_DatasetFree(pointer) }
53
74
  end
data/lib/lightgbm/ffi.rb CHANGED
@@ -12,6 +12,7 @@ module LightGBM
12
12
  # dataset
13
13
  attach_function :LGBM_DatasetCreateFromFile, %i[string string pointer pointer], :int
14
14
  attach_function :LGBM_DatasetCreateFromMat, %i[pointer int int32 int32 int string pointer pointer], :int
15
+ attach_function :LGBM_DatasetGetSubset, %i[pointer pointer int32 string pointer], :int
15
16
  attach_function :LGBM_DatasetFree, %i[pointer], :int
16
17
  attach_function :LGBM_DatasetSaveBinary, %i[pointer string], :int
17
18
  attach_function :LGBM_DatasetDumpText, %i[pointer string], :int
@@ -26,9 +27,15 @@ module LightGBM
26
27
  attach_function :LGBM_BoosterLoadModelFromString, %i[string pointer pointer], :int
27
28
  attach_function :LGBM_BoosterFree, %i[pointer], :int
28
29
  attach_function :LGBM_BoosterAddValidData, %i[pointer pointer], :int
30
+ attach_function :LGBM_BoosterGetNumClasses, %i[pointer pointer], :int
29
31
  attach_function :LGBM_BoosterUpdateOneIter, %i[pointer pointer], :int
30
32
  attach_function :LGBM_BoosterGetCurrentIteration, %i[pointer pointer], :int
33
+ attach_function :LGBM_BoosterNumModelPerIteration, %i[pointer pointer], :int
34
+ attach_function :LGBM_BoosterNumberOfTotalModel, %i[pointer pointer], :int
35
+ attach_function :LGBM_BoosterGetEvalCounts, %i[pointer pointer], :int
36
+ attach_function :LGBM_BoosterGetEvalNames, %i[pointer pointer pointer], :int
31
37
  attach_function :LGBM_BoosterGetNumFeature, %i[pointer pointer], :int
38
+ attach_function :LGBM_BoosterGetEval, %i[pointer int pointer pointer], :int
32
39
  attach_function :LGBM_BoosterPredictForMat, %i[pointer pointer int int32 int32 int int int string pointer pointer], :int
33
40
  attach_function :LGBM_BoosterSaveModel, %i[pointer int int string], :int
34
41
  attach_function :LGBM_BoosterSaveModelToString, %i[pointer int int int64 pointer pointer], :int
@@ -8,12 +8,20 @@ module LightGBM
8
8
 
9
9
  # remove spaces in keys and values to prevent injection
10
10
  def params_str(params)
11
- (params || {}).map { |k, v| [check_param(k.to_s), check_param(v.to_s)].join("=") }.join(" ")
11
+ params.map { |k, v| [check_param(k.to_s), check_param(Array(v).join(",").to_s)].join("=") }.join(" ")
12
12
  end
13
13
 
14
14
  def check_param(v)
15
15
  raise ArgumentError, "Invalid parameter" if /[[:space:]]/.match(v)
16
16
  v
17
17
  end
18
+
19
+ # change default verbosity
20
+ def set_verbosity(params)
21
+ params_keys = params.keys.map(&:to_s)
22
+ unless params_keys.include?("verbosity")
23
+ params["verbosity"] = -1
24
+ end
25
+ end
18
26
  end
19
27
  end
@@ -1,3 +1,3 @@
1
1
  module LightGBM
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lightgbm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-14 00:00:00.000000000 Z
11
+ date: 2019-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi