├── test ├── support │ ├── requirements.txt │ ├── dmatrix.py │ ├── ranker.py │ ├── regressor.py │ ├── booster.py │ ├── classifier.py │ ├── train.py │ ├── cv.py │ ├── data.csv │ └── model.json ├── ranker_test.rb ├── regressor_test.rb ├── cv_test.rb ├── train_test.rb ├── booster_test.rb ├── test_helper.rb ├── dmatrix_test.rb ├── classifier_test.rb └── callbacks_test.rb ├── lib ├── xgb.rb ├── xgboost │ ├── version.rb │ ├── ranker.rb │ ├── training_callback.rb │ ├── cv_pack.rb │ ├── regressor.rb │ ├── utils.rb │ ├── model.rb │ ├── packed_booster.rb │ ├── evaluation_monitor.rb │ ├── classifier.rb │ ├── ffi.rb │ ├── early_stopping.rb │ ├── callback_container.rb │ ├── dmatrix.rb │ └── booster.rb └── xgboost.rb ├── .gitignore ├── Gemfile ├── .github └── workflows │ └── build.yml ├── xgb.gemspec ├── NOTICE.txt ├── vendor.yml ├── Rakefile ├── CHANGELOG.md ├── README.md └── LICENSE.txt /test/support/requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | scikit-learn 3 | xgboost 4 | -------------------------------------------------------------------------------- /lib/xgb.rb: -------------------------------------------------------------------------------- 1 | require_relative "xgboost" 2 | 3 | # legacy 4 | Xgb = XGBoost 5 | -------------------------------------------------------------------------------- /lib/xgboost/version.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | VERSION = "0.11.0" 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | *.lock 10 | /vendor/ 11 | -------------------------------------------------------------------------------- /test/support/dmatrix.py: -------------------------------------------------------------------------------- 1 | import xgboost as xgb 2 | 3 | data = [[1, 2], [3, 4]] 4 | label = [1, 2] 5 | dataset = xgb.DMatrix(data, label=label) 6 | print(dataset.feature_names) 7 | print(dataset.feature_types) 8 | -------------------------------------------------------------------------------- /lib/xgboost/ranker.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class Ranker < Model 3 | def initialize(objective: "rank:ndcg", **options) 4 | super 5 | end 6 | 7 | def fit(x, y, group) 8 | dtrain = DMatrix.new(x, label: y) 9 | dtrain.group = group 10 | @booster = XGBoost.train(@params, dtrain, num_boost_round: @n_estimators) 11 | nil 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "rake" 6 | gem "minitest" 7 | gem "csv" 8 | gem "daru" 9 | gem "matrix" 10 | gem "rover-df", platform: [:mri, :windows] 11 | 12 | # TODO remove when numo-narray > 0.9.2.1 is released 13 | if Gem.win_platform? 14 | gem "numo-narray", github: "ruby-numo/numo-narray", ref: "421feddb46cac5145d69067fc1ac3ba3c434f668" 15 | else 16 | gem "numo-narray", platform: [:mri, :windows] 17 | end 18 | -------------------------------------------------------------------------------- /test/support/ranker.py: -------------------------------------------------------------------------------- 1 | import xgboost as xgb 2 | import pandas as pd 3 | 4 | df = pd.read_csv('test/support/data.csv') 5 | 6 | X = df.drop(columns=['y']) 7 | y = df['y'].replace(2, 1) 8 | 9 | X_train = X[:300] 10 | y_train = y[:300] 11 | X_test = X[300:] 12 | y_test = y[300:] 13 | 14 | group = [100, 200] 15 | 16 | model = xgb.XGBRanker() 17 | model.fit(X_train, y_train, group=group) 18 | print(model.predict(X_test)[0:6].tolist()) 19 | print(model.feature_importances_.tolist()) 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | strategy: 6 | fail-fast: false 7 | matrix: 8 | os: [ubuntu-latest, macos-latest, windows-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - uses: actions/checkout@v5 12 | - uses: ruby/setup-ruby@v1 13 | with: 14 | ruby-version: 3.4 15 | bundler-cache: true 16 | - if: ${{ startsWith(matrix.os, 'macos') }} 17 | run: brew install libomp 18 | - run: bundle exec rake vendor:platform 19 | - run: bundle exec rake test 20 | -------------------------------------------------------------------------------- /lib/xgboost/training_callback.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class TrainingCallback 3 | def before_training(model) 4 | # Run before training starts 5 | model 6 | end 7 | 8 | def after_training(model) 9 | # Run after training is finished 10 | model 11 | end 12 | 13 | def before_iteration(model, epoch, evals_log) 14 | # Run before each iteration. Returns true when training should stop. 15 | false 16 | end 17 | 18 | def after_iteration(model, epoch, evals_log) 19 | # Run after each iteration. Returns true when training should stop. 20 | false 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /xgb.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/xgboost/version" 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "xgb" 5 | spec.version = XGBoost::VERSION 6 | spec.summary = "High performance gradient boosting for Ruby" 7 | spec.homepage = "https://github.com/ankane/xgboost-ruby" 8 | spec.license = "Apache-2.0" 9 | 10 | spec.author = "Andrew Kane" 11 | spec.email = "andrew@ankane.org" 12 | 13 | spec.files = Dir["*.{md,txt}", "{lib,vendor}/**/*"] 14 | spec.require_path = "lib" 15 | 16 | spec.required_ruby_version = ">= 3.2" 17 | 18 | spec.add_dependency "ffi" 19 | end 20 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014-2025 XGBoost contributors 2 | Copyright 2019-2025 Andrew Kane 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /lib/xgboost/cv_pack.rb: -------------------------------------------------------------------------------- 1 | require "forwardable" 2 | 3 | module XGBoost 4 | class CVPack 5 | extend Forwardable 6 | 7 | def_delegators :@bst, :num_boosted_rounds, :best_iteration=, :best_score= 8 | 9 | attr_reader :bst 10 | 11 | def initialize(dtrain, dtest, param) 12 | @dtrain = dtrain 13 | @dtest = dtest 14 | @watchlist = [[dtrain, "train"], [dtest, "test"]] 15 | @bst = Booster.new(params: param, cache: [dtrain, dtest]) 16 | end 17 | 18 | def update(iteration) 19 | @bst.update(@dtrain, iteration) 20 | end 21 | 22 | def eval_set(iteration) 23 | @bst.eval_set(@watchlist, iteration) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/support/regressor.py: -------------------------------------------------------------------------------- 1 | import xgboost as xgb 2 | import pandas as pd 3 | 4 | df = pd.read_csv('test/support/data.csv') 5 | 6 | X = df.drop(columns=['y']) 7 | y = df['y'] 8 | 9 | X_train = X[:300] 10 | y_train = y[:300] 11 | X_test = X[300:] 12 | y_test = y[300:] 13 | 14 | model = xgb.XGBRegressor() 15 | model.fit(X_train, y_train) 16 | 17 | print('predict', model.predict(X_test)[0:6].tolist()) 18 | 19 | print('feature_importances', model.feature_importances_.tolist()) 20 | 21 | print('early_stopping') 22 | model = xgb.XGBRegressor(early_stopping_rounds=5) 23 | model.fit(X_train, y_train, eval_set=[(X_test, y_test)], verbose=True) 24 | print(model.get_booster().best_iteration) 25 | -------------------------------------------------------------------------------- /lib/xgboost/regressor.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class Regressor < Model 3 | def initialize(objective: "reg:squarederror", **options) 4 | super 5 | end 6 | 7 | def fit(x, y, eval_set: nil, early_stopping_rounds: nil, verbose: true) 8 | dtrain = DMatrix.new(x, label: y) 9 | evals = Array(eval_set).map.with_index { |v, i| [DMatrix.new(v[0], label: v[1]), "validation_#{i}"] } 10 | 11 | @booster = XGBoost.train(@params, dtrain, 12 | num_boost_round: @n_estimators, 13 | early_stopping_rounds: early_stopping_rounds || @early_stopping_rounds, 14 | verbose_eval: verbose, 15 | evals: evals 16 | ) 17 | nil 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/support/booster.py: -------------------------------------------------------------------------------- 1 | import xgboost as xgb 2 | import pandas as pd 3 | import json 4 | 5 | df = pd.read_csv('test/support/data.csv') 6 | 7 | X = df.drop(columns=['y']) 8 | y = df['y'] 9 | 10 | X_train = X[:300] 11 | y_train = y[:300] 12 | X_test = X[300:] 13 | y_test = y[300:] 14 | 15 | train_data = xgb.DMatrix(X_train, label=y_train) 16 | bst = xgb.train({}, train_data) 17 | bst.save_model('test/support/model.json') 18 | 19 | bst = xgb.Booster(model_file='test/support/model.json') 20 | print('score', bst.get_score()) 21 | bst.dump_model('/tmp/model.json', dump_format='json') 22 | 23 | with open('/tmp/model.json') as f: 24 | booster_dump = json.load(f)[0] 25 | 26 | print('split', booster_dump['split']) 27 | 28 | print('attributes', bst.attributes()) 29 | -------------------------------------------------------------------------------- /test/ranker_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class RankerTest < Minitest::Test 4 | def test_works 5 | skip "Inconsistent results on different platforms" if ENV["CI"] 6 | 7 | x_train, y_train, x_test, _ = binary_data 8 | group = [100, 200] 9 | 10 | model = XGBoost::Ranker.new 11 | model.fit(x_train, y_train, group) 12 | y_pred = model.predict(x_test) 13 | expected = [-1.2509069442749023, 1.5171653032302856, 1.5171653032302856, 1.5171653032302856, 1.5171653032302856, 1.5171653032302856] 14 | assert_elements_in_delta expected, y_pred.first(6) 15 | 16 | expected = [0.0, 0.19046767055988312, 0.8095322847366333, 0.0] 17 | assert_elements_in_delta expected, model.feature_importances 18 | 19 | model.save_model(tempfile) 20 | 21 | model = XGBoost::Ranker.new 22 | model.load_model(tempfile) 23 | assert_equal y_pred, model.predict(x_test) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/xgboost/utils.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | module Utils 3 | private 4 | 5 | def check_call(err) 6 | if err != 0 7 | # make friendly 8 | message = FFI.XGBGetLastError.split("\n").first.split(/:\d+: /, 2).last 9 | raise XGBoost::Error, message 10 | end 11 | end 12 | 13 | def array_of_pointers(values) 14 | arr = ::FFI::MemoryPointer.new(:pointer, values.size) 15 | arr.write_array_of_pointer(values) 16 | # keep reference for string pointers 17 | arr.instance_variable_set(:@xgboost_ref, values) 18 | arr 19 | end 20 | 21 | def string_pointer(value) 22 | ::FFI::MemoryPointer.from_string(value.to_s) 23 | end 24 | 25 | def from_cstr_to_rbstr(data, length) 26 | data.read_pointer.read_array_of_pointer(length.read_uint64).map do |ptr| 27 | ptr.read_string.force_encoding(Encoding::UTF_8) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/xgboost/model.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class Model 3 | attr_reader :booster 4 | 5 | def initialize(n_estimators: 100, importance_type: "gain", early_stopping_rounds: nil, **options) 6 | @params = options 7 | @n_estimators = n_estimators 8 | @importance_type = importance_type 9 | @early_stopping_rounds = early_stopping_rounds 10 | end 11 | 12 | def predict(data) 13 | dmat = DMatrix.new(data) 14 | @booster.predict(dmat) 15 | end 16 | 17 | def save_model(fname) 18 | @booster.save_model(fname) 19 | end 20 | 21 | def load_model(fname) 22 | @booster = Booster.new(model_file: fname) 23 | end 24 | 25 | def feature_importances 26 | score = @booster.score(importance_type: @importance_type) 27 | feature_names = @booster.feature_names || @booster.num_features.times.map { |i| "f#{i}" } 28 | scores = feature_names.map { |k| score[k] || 0.0 } 29 | total = scores.sum.to_f 30 | scores.map { |s| s / total } 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/xgboost/packed_booster.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class PackedBooster 3 | def initialize(cvfolds) 4 | @cvfolds = cvfolds 5 | end 6 | 7 | def update(iteration) 8 | @cvfolds.each do |fold| 9 | fold.update(iteration) 10 | end 11 | end 12 | 13 | def set_attr(**kwargs) 14 | @cvfolds.each do |f| 15 | f.bst.set_attr(**kwargs) 16 | end 17 | end 18 | 19 | def attr(key) 20 | @cvfolds[0].bst.attr(key) 21 | end 22 | 23 | def eval_set(iteration) 24 | @cvfolds.map { |f| f.eval_set(iteration) } 25 | end 26 | 27 | def best_iteration 28 | @cvfolds[0].bst.best_iteration 29 | end 30 | 31 | def best_iteration=(iteration) 32 | @cvfolds.each do |fold| 33 | fold.best_iteration = iteration 34 | end 35 | end 36 | 37 | def best_score 38 | @cvfolds[0].bst.best_score 39 | end 40 | 41 | def best_score=(score) 42 | @cvfolds.each do |fold| 43 | fold.best_score = score 44 | end 45 | end 46 | 47 | def num_boosted_rounds 48 | @cvfolds[0].num_boosted_rounds 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/xgboost/evaluation_monitor.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class EvaluationMonitor < TrainingCallback 3 | def initialize(period:, show_stdv: false) 4 | @show_stdv = show_stdv 5 | @period = period 6 | end 7 | 8 | def after_iteration(model, epoch, evals_log) 9 | if evals_log.empty? 10 | return false 11 | end 12 | 13 | msg = "[#{epoch}]" 14 | evals_log.each do |data, metric| 15 | metric.each do |metric_name, log| 16 | stdv = nil 17 | if log[-1].is_a?(Array) 18 | score = log[-1][0] 19 | stdv = log[-1][1] 20 | else 21 | score = log[-1] 22 | end 23 | msg += fmt_metric(data, metric_name, score, stdv) 24 | end 25 | end 26 | msg += "\n" 27 | 28 | if epoch % @period == 0 29 | puts msg 30 | end 31 | false 32 | end 33 | 34 | private 35 | 36 | def fmt_metric(data, metric, score, std) 37 | if !std.nil? && @show_stdv 38 | "\t%s:%.5f+%.5f" % [data + "-" + metric, score, std] 39 | else 40 | "\t%s:%.5f" % [data + "-" + metric, score] 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /vendor.yml: -------------------------------------------------------------------------------- 1 | platforms: 2 | x86_64-linux: 3 | url: https://github.com/ankane/ml-builds/releases/download/xgboost-3.1.0/xgboost-3.1.0-x86_64-linux.zip 4 | sha256: ede61d3c21cca9d7b73caa68e6f771b362f38f97f94e115077d6c5a5ee1fc37c 5 | x86_64-linux-musl: 6 | url: https://github.com/ankane/ml-builds/releases/download/xgboost-3.1.0/xgboost-3.1.0-x86_64-linux-musl.zip 7 | sha256: 30bea514d6c36fd3001d83131e15c987c33bd3a185facdb9d6bf47fdc8a135f6 8 | aarch64-linux: 9 | url: https://github.com/ankane/ml-builds/releases/download/xgboost-3.1.0/xgboost-3.1.0-aarch64-linux.zip 10 | sha256: 6b119dca6c0eb8b22689e5237ffde9c3ceef0b2bcc8b0cf0603deccaea59ec7b 11 | x86_64-darwin: 12 | url: https://github.com/ankane/ml-builds/releases/download/xgboost-3.1.0/xgboost-3.1.0-x86_64-darwin.zip 13 | sha256: dabf1654b8d40043bf0730ee527e5266b9ae0b7bf7890d3b386c822b84646556 14 | arm64-darwin: 15 | url: https://github.com/ankane/ml-builds/releases/download/xgboost-3.1.0/xgboost-3.1.0-aarch64-darwin.zip 16 | sha256: 673d479dcc2305416258389df6ff889749e97d7592ab24a653fa87ff253c2df4 17 | x64-mingw: 18 | url: https://github.com/ankane/ml-builds/releases/download/xgboost-3.1.0/xgboost-3.1.0-x86_64-windows.zip 19 | sha256: b1d2c7f26aa319a6bfe866b452a1f948672a6db22a688a90dd2a62020fc51e68 20 | -------------------------------------------------------------------------------- /test/support/classifier.py: -------------------------------------------------------------------------------- 1 | import xgboost as xgb 2 | import pandas as pd 3 | 4 | df = pd.read_csv('test/support/data.csv') 5 | 6 | X = df.drop(columns=['y']) 7 | yb = df['y'].replace(2, 1) 8 | ym = df['y'] 9 | 10 | X_train = X[:300] 11 | yb_train = yb[:300] 12 | ym_train = ym[:300] 13 | X_test = X[300:] 14 | yb_test = yb[300:] 15 | ym_test = ym[300:] 16 | 17 | print('test_binary') 18 | 19 | model = xgb.XGBClassifier() 20 | model.fit(X_train, yb_train) 21 | print(model.predict(X_test)[0:100].tolist()) 22 | print(model.predict_proba(X_test)[0].tolist()) 23 | print(model.feature_importances_.tolist()) 24 | 25 | print() 26 | print('test_multiclass') 27 | 28 | model = xgb.XGBClassifier() 29 | model.fit(X_train, ym_train) 30 | print(model.predict(X_test)[0:100].tolist()) 31 | print(model.predict_proba(X_test)[0].tolist()) 32 | print(model.feature_importances_.tolist()) 33 | 34 | print() 35 | print('test_early_stopping') 36 | model = xgb.XGBClassifier(early_stopping_rounds=5) 37 | model.fit(X_train, ym_train, eval_set=[(X_test, ym_test)]) 38 | print(model.get_booster().best_iteration) 39 | 40 | print() 41 | print('test_missing') 42 | 43 | X_train_miss = X_train.copy() 44 | X_test_miss = X_test.copy() 45 | X_train_miss[X_train_miss == 3.7] = None 46 | X_test_miss[X_test_miss == 3.7] = None 47 | model = xgb.XGBClassifier() 48 | model.fit(X_train_miss, ym_train) 49 | print(model.predict(X_test_miss)[0:100].tolist()) 50 | print(model.feature_importances_.tolist()) 51 | -------------------------------------------------------------------------------- /lib/xgboost/classifier.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class Classifier < Model 3 | def initialize(objective: "binary:logistic", **options) 4 | super 5 | end 6 | 7 | def fit(x, y, eval_set: nil, early_stopping_rounds: nil, verbose: true) 8 | n_classes = y.uniq.size 9 | 10 | params = @params.dup 11 | if n_classes > 2 12 | params[:objective] = "multi:softprob" 13 | params[:num_class] = n_classes 14 | end 15 | 16 | dtrain = DMatrix.new(x, label: y) 17 | evals = Array(eval_set).map.with_index { |v, i| [DMatrix.new(v[0], label: v[1]), "validation_#{i}"] } 18 | 19 | @booster = XGBoost.train(params, dtrain, 20 | num_boost_round: @n_estimators, 21 | early_stopping_rounds: early_stopping_rounds || @early_stopping_rounds, 22 | verbose_eval: verbose, 23 | evals: evals 24 | ) 25 | nil 26 | end 27 | 28 | def predict(data) 29 | y_pred = super(data) 30 | 31 | if y_pred.first.is_a?(Array) 32 | # multiple classes 33 | y_pred.map do |v| 34 | v.map.with_index.max_by { |v2, _| v2 }.last 35 | end 36 | else 37 | y_pred.map { |v| v > 0.5 ? 1 : 0 } 38 | end 39 | end 40 | 41 | def predict_proba(data) 42 | dmat = DMatrix.new(data) 43 | y_pred = @booster.predict(dmat) 44 | 45 | if y_pred.first.is_a?(Array) 46 | # multiple classes 47 | y_pred 48 | else 49 | y_pred.map { |v| [1 - v, v] } 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/support/train.py: -------------------------------------------------------------------------------- 1 | import xgboost as xgb 2 | import pandas as pd 3 | import numpy as np 4 | 5 | df = pd.read_csv('test/support/data.csv') 6 | 7 | X = df.drop(columns=['y']) 8 | y = df['y'] 9 | 10 | X_train = X[:300] 11 | y_train = y[:300] 12 | X_test = X[300:] 13 | y_test = y[300:] 14 | 15 | print('test_regression') 16 | 17 | regression_params = {'objective': 'reg:squarederror'} 18 | regression_train = xgb.DMatrix(X_train, label=y_train) 19 | regression_test = xgb.DMatrix(X_test, label=y_test) 20 | bst = xgb.train(regression_params, regression_train) 21 | y_pred = bst.predict(regression_test) 22 | print(np.sqrt(np.mean((y_pred - y_test)**2))) 23 | 24 | print('') 25 | print('test_binary') 26 | 27 | binary_params = {'objective': 'binary:logistic'} 28 | binary_train = xgb.DMatrix(X_train, label=y_train.replace(2, 1)) 29 | binary_test = xgb.DMatrix(X_test, label=y_test.replace(2, 1)) 30 | bst = xgb.train(binary_params, binary_train) 31 | y_pred = bst.predict(binary_test) 32 | print(y_pred[0]) 33 | 34 | print('') 35 | print('test_multiclass') 36 | 37 | multiclass_params = {'objective': 'multi:softprob', 'num_class': 3} 38 | multiclass_train = xgb.DMatrix(X_train, label=y_train) 39 | multiclass_test = xgb.DMatrix(X_test, label=y_test) 40 | bst = xgb.train(multiclass_params, multiclass_train) 41 | y_pred = bst.predict(multiclass_test) 42 | print(y_pred[0].tolist()) 43 | 44 | print('') 45 | print('test_early_stopping_early') 46 | 47 | bst = xgb.train(regression_params, regression_train, num_boost_round=100, evals=[(regression_train, 'train'), (regression_test, 'test')], early_stopping_rounds=5) 48 | print(bst.best_iteration) 49 | -------------------------------------------------------------------------------- /test/regressor_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class RegressorTest < Minitest::Test 4 | def test_works 5 | x_train, y_train, x_test, _ = regression_data 6 | 7 | model = XGBoost::Regressor.new 8 | model.fit(x_train, y_train) 9 | y_pred = model.predict(x_test) 10 | expected = [1.1507850885391235, 1.3788503408432007, 1.66087806224823, 0.5367319583892822, 1.0890085697174072, 1.3380072116851807] 11 | assert_elements_in_delta expected, y_pred.first(6) 12 | 13 | expected = [0.10230803489685059, 0.3298805356025696, 0.4885728359222412, 0.07923857867717743] 14 | assert_elements_in_delta expected, model.feature_importances 15 | 16 | model.save_model(tempfile) 17 | 18 | model = XGBoost::Regressor.new 19 | model.load_model(tempfile) 20 | assert_equal y_pred, model.predict(x_test) 21 | end 22 | 23 | def test_early_stopping 24 | x_train, y_train, x_test, y_test = regression_data 25 | 26 | model = XGBoost::Regressor.new(early_stopping_rounds: 5) 27 | model.fit(x_train, y_train, eval_set: [[x_test, y_test]], verbose: false) 28 | assert_equal 9, model.booster.best_iteration 29 | end 30 | 31 | def test_daru 32 | data = Daru::DataFrame.from_csv(data_path) 33 | y = data["y"] 34 | x = data.delete_vector("y") 35 | 36 | # daru has bug with 0...300 37 | x_train = x.row[0..299] 38 | y_train = y[0..299] 39 | x_test = x.row[300..-1] 40 | 41 | model = XGBoost::Regressor.new 42 | model.fit(x_train, y_train) 43 | y_pred = model.predict(x_test) 44 | expected = [1.1507850885391235, 1.3788503408432007, 1.66087806224823, 0.5367319583892822, 1.0890085697174072, 1.3380072116851807] 45 | assert_elements_in_delta expected, y_pred.first(6) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/support/cv.py: -------------------------------------------------------------------------------- 1 | import xgboost as xgb 2 | import pandas as pd 3 | 4 | df = pd.read_csv('test/support/data.csv') 5 | 6 | X = df.drop(columns=['y']) 7 | y = df['y'] 8 | 9 | X_train = X[:300] 10 | y_train = y[:300] 11 | X_test = X[300:] 12 | y_test = y[300:] 13 | 14 | print('test_regression') 15 | 16 | regression_params = {'objective': 'reg:squarederror'} 17 | regression_train = xgb.DMatrix(X_train, label=y_train) 18 | eval_hist = xgb.cv(regression_params, regression_train, shuffle=False, as_pandas=False) 19 | for k in ['train-rmse-mean', 'train-rmse-std', 'test-rmse-mean', 'test-rmse-std']: 20 | print(k, eval_hist[k][0]) 21 | print(k, eval_hist[k][-1]) 22 | 23 | print() 24 | print('test_binary') 25 | 26 | binary_params = {'objective': 'binary:logistic'} 27 | binary_train = xgb.DMatrix(X_train, label=y_train.replace(2, 1)) 28 | eval_hist = xgb.cv(binary_params, binary_train, shuffle=False, as_pandas=False) 29 | for k in ['train-logloss-mean', 'train-logloss-std', 'test-logloss-mean', 'test-logloss-std']: 30 | print("%-20s %f" % (k, eval_hist[k][0])) 31 | print("%-20s %f" % (k, eval_hist[k][-1])) 32 | 33 | print() 34 | print('test_multiclass') 35 | 36 | multiclass_params = {'objective': 'multi:softprob', 'num_class': 3} 37 | multiclass_train = xgb.DMatrix(X_train, label=y_train) 38 | eval_hist = xgb.cv(multiclass_params, multiclass_train, shuffle=False, as_pandas=False) 39 | for k in ['train-mlogloss-mean', 'train-mlogloss-std', 'test-mlogloss-mean', 'test-mlogloss-std']: 40 | print("%-20s %f" % (k, eval_hist[k][0])) 41 | print("%-20s %f" % (k, eval_hist[k][-1])) 42 | 43 | print('') 44 | print('test_early_stopping_early') 45 | 46 | eval_hist = xgb.cv(regression_params, regression_train, shuffle=False, as_pandas=False, verbose_eval=True, num_boost_round=100, early_stopping_rounds=5) 47 | print(len(eval_hist['train-rmse-mean'])) 48 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new do |t| 5 | t.pattern = "test/**/*_test.rb" 6 | t.warning = false # for daru 7 | end 8 | 9 | task default: :test 10 | 11 | # ensure vendor files exist 12 | task :ensure_vendor do 13 | vendor_config.fetch("platforms").each_key do |k| 14 | raise "Missing directory: #{k}" unless Dir.exist?("vendor/#{k}") 15 | end 16 | end 17 | 18 | Rake::Task["build"].enhance [:ensure_vendor] 19 | 20 | def download_platform(platform) 21 | require "fileutils" 22 | require "open-uri" 23 | require "tmpdir" 24 | 25 | config = vendor_config.fetch("platforms").fetch(platform) 26 | url = config.fetch("url") 27 | sha256 = config.fetch("sha256") 28 | 29 | puts "Downloading #{url}..." 30 | contents = URI.parse(url).read 31 | 32 | computed_sha256 = Digest::SHA256.hexdigest(contents) 33 | raise "Bad hash: #{computed_sha256}" if computed_sha256 != sha256 34 | 35 | file = Tempfile.new(binmode: true) 36 | file.write(contents) 37 | 38 | vendor = File.expand_path("vendor", __dir__) 39 | FileUtils.mkdir_p(vendor) 40 | 41 | dest = File.join(vendor, platform) 42 | FileUtils.rm_r(dest) if Dir.exist?(dest) 43 | 44 | # run apt install unzip on Linux 45 | system "unzip", "-q", file.path, "-d", dest, exception: true 46 | end 47 | 48 | def vendor_config 49 | @vendor_config ||= begin 50 | require "yaml" 51 | YAML.safe_load_file("vendor.yml") 52 | end 53 | end 54 | 55 | namespace :vendor do 56 | task :all do 57 | vendor_config.fetch("platforms").each_key do |k| 58 | download_platform(k) 59 | end 60 | end 61 | 62 | task :platform do 63 | if Gem.win_platform? 64 | download_platform("x64-mingw") 65 | elsif RbConfig::CONFIG["host_os"] =~ /darwin/i 66 | if RbConfig::CONFIG["host_cpu"] =~ /arm|aarch64/i 67 | download_platform("arm64-darwin") 68 | else 69 | download_platform("x86_64-darwin") 70 | end 71 | elsif RbConfig::CONFIG["host_os"] =~ /linux-musl/i 72 | download_platform("x86_64-linux-musl") 73 | else 74 | if RbConfig::CONFIG["host_cpu"] =~ /arm|aarch64/i 75 | download_platform("aarch64-linux") 76 | else 77 | download_platform("x86_64-linux") 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/cv_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class CvTest < Minitest::Test 4 | def test_regression 5 | eval_hist = XGBoost.cv(regression_params, regression_train, shuffle: false) 6 | assert_in_delta 0.40130647066587777, eval_hist["train-rmse-mean"].first 7 | assert_in_delta 0.05131705086428223, eval_hist["train-rmse-mean"].last 8 | assert_in_delta 0.005782070203164552, eval_hist["train-rmse-std"].first 9 | assert_in_delta 0.002585536557357405, eval_hist["train-rmse-std"].last 10 | assert_in_delta 0.4549188381629605, eval_hist["test-rmse-mean"].first 11 | assert_in_delta 0.3262552684991104, eval_hist["test-rmse-mean"].last 12 | assert_in_delta 0.028011817754340963, eval_hist["test-rmse-std"].first 13 | assert_in_delta 0.009954421852476214, eval_hist["test-rmse-std"].last 14 | end 15 | 16 | def test_binary 17 | eval_hist = XGBoost.cv(binary_params, binary_train, shuffle: false) 18 | assert_in_delta 0.290831, eval_hist["train-logloss-mean"].first 19 | assert_in_delta 0.069389, eval_hist["train-logloss-mean"].last 20 | assert_in_delta 0.029254, eval_hist["train-logloss-std"].first 21 | assert_in_delta 0.004453, eval_hist["train-logloss-std"].last 22 | assert_in_delta 0.324324, eval_hist["test-logloss-mean"].first 23 | assert_in_delta 0.151608, eval_hist["test-logloss-mean"].last 24 | assert_in_delta 0.049756, eval_hist["test-logloss-std"].first 25 | assert_in_delta 0.039735, eval_hist["test-logloss-std"].last 26 | end 27 | 28 | def test_multiclass 29 | eval_hist = XGBoost.cv(multiclass_params, multiclass_train, shuffle: false) 30 | assert_in_delta 0.677055, eval_hist["train-mlogloss-mean"].first 31 | assert_in_delta 0.114060, eval_hist["train-mlogloss-mean"].last 32 | assert_in_delta 0.006876, eval_hist["train-mlogloss-std"].first 33 | assert_in_delta 0.007326, eval_hist["train-mlogloss-std"].last 34 | assert_in_delta 0.734124, eval_hist["test-mlogloss-mean"].first 35 | assert_in_delta 0.375596, eval_hist["test-mlogloss-mean"].last 36 | assert_in_delta 0.010682, eval_hist["test-mlogloss-std"].first 37 | assert_in_delta 0.029593, eval_hist["test-mlogloss-std"].last 38 | end 39 | 40 | def test_early_stopping_early 41 | eval_hist = XGBoost.cv(regression_params, regression_train, shuffle: false, num_boost_round: 100, early_stopping_rounds: 5) 42 | assert_equal 10, eval_hist["train-rmse-mean"].size 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/train_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class TrainTest < Minitest::Test 4 | def test_regression 5 | model = XGBoost.train(regression_params, regression_train) 6 | y_pred = model.predict(regression_test) 7 | assert_operator rsme(regression_test.label, y_pred), :<=, 0.32 8 | 9 | model.save_model(tempfile) 10 | model = XGBoost::Booster.new(model_file: tempfile) 11 | y_pred = model.predict(regression_test) 12 | assert_operator rsme(regression_test.label, y_pred), :<=, 0.32 13 | end 14 | 15 | def test_binary 16 | model = XGBoost.train(binary_params, binary_train) 17 | y_pred = model.predict(binary_test) 18 | assert_in_delta 0.9892826, y_pred.first 19 | assert_equal 200, y_pred.size 20 | 21 | model.save_model(tempfile) 22 | model = XGBoost::Booster.new(model_file: tempfile) 23 | y_pred2 = model.predict(binary_test) 24 | assert_equal y_pred, y_pred2 25 | end 26 | 27 | def test_multiclass 28 | model = XGBoost.train(multiclass_params, multiclass_train) 29 | 30 | y_pred = model.predict(multiclass_test) 31 | expected = [0.060599446296691895, 0.8754178881645203, 0.06398269534111023] 32 | assert_elements_in_delta expected, y_pred.first 33 | # ensure reshaped 34 | assert_equal 200, y_pred.size 35 | assert_equal 3, y_pred.first.size 36 | 37 | model.save_model(tempfile) 38 | model = XGBoost::Booster.new(model_file: tempfile) 39 | y_pred2 = model.predict(multiclass_test) 40 | assert_equal y_pred, y_pred2 41 | end 42 | 43 | def test_early_stopping_early 44 | model = XGBoost.train(regression_params, regression_train, num_boost_round: 100, evals: [[regression_train, "train"], [regression_test, "eval"]], early_stopping_rounds: 5, verbose_eval: false) 45 | assert_equal 9, model.best_iteration 46 | end 47 | 48 | def test_evals_result 49 | evals_result = {} 50 | XGBoost.train(regression_params, regression_train, evals: [[regression_train, "train"], [regression_test, "eval"]], evals_result: evals_result, verbose_eval: false) 51 | assert evals_result["train"]["rmse"] 52 | end 53 | 54 | def test_lib_version 55 | assert_match(/\A\d+\.\d+\.\d+\z/, XGBoost.lib_version) 56 | end 57 | 58 | def test_feature_names_and_types 59 | model = XGBoost.train(regression_params, regression_train) 60 | assert_nil model.feature_names 61 | assert_nil model.feature_types 62 | end 63 | 64 | private 65 | 66 | def rsme(y_true, y_pred) 67 | Math.sqrt(y_true.zip(y_pred).map { |a, b| (a - b)**2 }.sum / y_true.size.to_f) 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/booster_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class BoosterTest < Minitest::Test 4 | def test_dump_text 5 | assert_match(/0:\[x2 118, "f2" => 93, "f1" => 104, "f3" => 43} 42 | assert_equal expected.values.sort, booster.score.values.sort 43 | end 44 | 45 | def test_fscore 46 | assert_equal booster.score, booster.fscore 47 | end 48 | 49 | def test_attributes 50 | default_attributes = {} 51 | 52 | assert_nil booster["foo"] 53 | assert_equal(default_attributes, booster.attributes) 54 | 55 | booster["foo"] = "bar" 56 | 57 | assert_equal "bar", booster["foo"] 58 | assert_equal(default_attributes.merge("foo" => "bar"), booster.attributes) 59 | 60 | booster["foo"] = "baz" 61 | 62 | assert_equal "baz", booster["foo"] 63 | assert_equal(default_attributes.merge("foo" => "baz"), booster.attributes) 64 | 65 | booster["bar"] = "qux" 66 | 67 | assert_equal(default_attributes.merge("foo" => "baz", "bar" => "qux"), booster.attributes) 68 | 69 | booster["foo"] = nil 70 | 71 | refute_includes(booster.attributes, "foo") 72 | end 73 | 74 | def test_copy 75 | booster.dup 76 | booster.clone 77 | end 78 | 79 | private 80 | 81 | def load_booster 82 | XGBoost::Booster.new(model_file: "test/support/model.json") 83 | end 84 | 85 | def booster 86 | @booster ||= load_booster 87 | end 88 | 89 | def booster_with_feature_names 90 | @booster_with_feature_names ||= load_booster.tap do |booster| 91 | booster.feature_names = 4.times.map { |idx| "feat#{idx}" } 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | Bundler.require(:default) 3 | require "minitest/autorun" 4 | require "json" 5 | 6 | class Minitest::Test 7 | def setup 8 | if stress? 9 | # autoload before GC.stress 10 | XGBoost::FFI.ffi_libraries 11 | load_data 12 | GC.stress = true 13 | end 14 | end 15 | 16 | def teardown 17 | GC.stress = false if stress? 18 | @tempfile = nil 19 | end 20 | 21 | def stress? 22 | ENV["STRESS"] 23 | end 24 | 25 | def assert_elements_in_delta(expected, actual) 26 | assert_equal expected.size, actual.size 27 | expected.zip(actual) do |exp, act| 28 | assert_in_delta exp, act 29 | end 30 | end 31 | 32 | def regression_data 33 | @regression_data ||= split_data(*load_data) 34 | end 35 | 36 | def regression_train 37 | @regression_train ||= split_train(regression_data) 38 | end 39 | 40 | def regression_test 41 | @regression_test ||= split_test(regression_data) 42 | end 43 | 44 | def binary_data 45 | x, y = load_data 46 | y = y.map { |v| v > 1 ? 1 : v } 47 | split_data(x, y) 48 | end 49 | 50 | def binary_train 51 | @binary_train ||= split_train(binary_data) 52 | end 53 | 54 | def binary_test 55 | @binary_test ||= split_test(binary_data) 56 | end 57 | 58 | def multiclass_data 59 | @multiclass_data ||= split_data(*load_data) 60 | end 61 | 62 | def multiclass_train 63 | @multiclass_train ||= split_train(multiclass_data) 64 | end 65 | 66 | def multiclass_test 67 | @multiclass_test ||= split_test(multiclass_data) 68 | end 69 | 70 | def ranker_data 71 | @ranker_data ||= binary_data 72 | end 73 | 74 | def data_path 75 | "test/support/data.csv" 76 | end 77 | 78 | def load_data 79 | @@load_data ||= begin 80 | x = [] 81 | y = [] 82 | CSV.foreach(data_path, headers: true, converters: :numeric) do |row| 83 | x << row.values_at("x0", "x1", "x2", "x3").freeze 84 | y << row["y"] 85 | end 86 | [x.freeze, y.freeze] 87 | end 88 | end 89 | 90 | def split_data(x, y) 91 | [x[0...300], y[0...300], x[300..-1], y[300..-1]] 92 | end 93 | 94 | def split_train(data) 95 | x_train, y_train, _, _ = data 96 | XGBoost::DMatrix.new(x_train, label: y_train) 97 | end 98 | 99 | def split_test(data) 100 | _, _, x_test, y_test = data 101 | XGBoost::DMatrix.new(x_test, label: y_test) 102 | end 103 | 104 | def regression_params 105 | {objective: "reg:squarederror"} 106 | end 107 | 108 | def binary_params 109 | {objective: "binary:logistic"} 110 | end 111 | 112 | def multiclass_params 113 | {objective: "multi:softprob", num_class: 3} 114 | end 115 | 116 | def tempfile 117 | @tempfile ||= "#{Dir.mktmpdir}/#{Time.now.to_f}.json" 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.11.0 (2025-10-18) 2 | 3 | - Updated XGBoost to 3.1.0 4 | - Dropped support for Ruby < 3.2 5 | 6 | ## 0.10.0 (2025-03-15) 7 | 8 | - Updated XGBoost to 3.0.0 9 | 10 | ## 0.9.0 (2024-10-17) 11 | 12 | - Updated XGBoost to 2.1.1 13 | - Added support for callbacks 14 | - Added `num_features` and `save_config` methods to `Booster` 15 | - Added `num_nonmissing` and `data_split_mode` methods to `DMatrix` 16 | - Dropped support for Ruby < 3.1 17 | 18 | ## 0.8.0 (2023-09-13) 19 | 20 | - Updated XGBoost to 2.0.0 21 | - Dropped support for Ruby < 3 22 | 23 | ## 0.7.3 (2023-07-24) 24 | 25 | - Fixed error with `dup` and `clone` 26 | 27 | ## 0.7.2 (2023-05-12) 28 | 29 | - Updated XGBoost to 1.7.5 30 | - Added musl shared library for Linux 31 | - Improved error message for invalid matrix 32 | 33 | ## 0.7.1 (2022-10-31) 34 | 35 | - Updated XGBoost to 1.7.0 36 | 37 | ## 0.7.0 (2022-06-05) 38 | 39 | - Updated XGBoost to 1.6.1 40 | - Improved ARM detection 41 | - Dropped support for Ruby < 2.7 42 | 43 | ## 0.6.0 (2021-10-23) 44 | 45 | - Updated XGBoost to 1.5.0 46 | 47 | ## 0.5.3 (2021-05-12) 48 | 49 | - Updated XGBoost to 1.4.0 50 | - Added ARM shared library for Linux 51 | 52 | ## 0.5.2 (2021-03-09) 53 | 54 | - Added ARM shared library for Mac 55 | 56 | ## 0.5.1 (2021-02-08) 57 | 58 | - Fixed error with validation sets without early stopping 59 | 60 | ## 0.5.0 (2020-12-12) 61 | 62 | - Updated XGBoost to 1.3.0 63 | 64 | ## 0.4.1 (2020-08-26) 65 | 66 | - Updated XGBoost to 1.2.0 67 | 68 | ## 0.4.0 (2020-05-17) 69 | 70 | - Updated XGBoost to 1.1.0 71 | - Changed default `learning_rate` and `max_depth` for Scikit-Learn API to match Python 72 | - Added support for Rover 73 | - Improved performance of Numo datasets 74 | - Improved error message when OpenMP not found on Mac 75 | 76 | ## 0.3.1 (2020-04-16) 77 | 78 | - Added `feature_names` and `feature_types` to `DMatrix` 79 | - Added feature names to `dump` 80 | 81 | ## 0.3.0 (2020-02-19) 82 | 83 | - Updated XGBoost to 1.0.0 84 | 85 | ## 0.2.1 (2020-02-11) 86 | 87 | - Fixed `Could not find XGBoost` error on some Linux platforms 88 | - Fixed `SignalException` on Windows 89 | 90 | ## 0.2.0 (2020-01-26) 91 | 92 | - Prefer `XGBoost` over `Xgb` 93 | - Changed to Apache 2.0 license to match XGBoost 94 | - Added shared libraries 95 | - Added support for booster attributes 96 | 97 | ## 0.1.3 (2019-10-27) 98 | 99 | - Added support for missing values 100 | - Fixed Daru training and prediction 101 | - Fixed error with JRuby 102 | 103 | ## 0.1.2 (2019-08-19) 104 | 105 | - Friendlier message when XGBoost not found 106 | - Free memory when objects are destroyed 107 | - Added `Ranker` 108 | - Added early stopping to Scikit-Learn API 109 | 110 | ## 0.1.1 (2019-08-16) 111 | 112 | - Added Scikit-Learn API 113 | - Added early stopping 114 | - Added `cv` method 115 | - Added support for Daru and Numo::NArray 116 | - Added many other methods 117 | - Fixed shape of multiclass predictions when loaded from file 118 | 119 | ## 0.1.0 (2019-08-15) 120 | 121 | - First release 122 | -------------------------------------------------------------------------------- /test/dmatrix_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class DMatrixTest < Minitest::Test 4 | def test_label 5 | data = [[1, 2], [3, 4]] 6 | label = [1, 2] 7 | dataset = XGBoost::DMatrix.new(data, label: label) 8 | assert_equal label, dataset.label 9 | end 10 | 11 | def test_weight 12 | data = [[1, 2], [3, 4]] 13 | weight = [1, 2] 14 | dataset = XGBoost::DMatrix.new(data, weight: weight) 15 | assert_equal weight, dataset.weight 16 | end 17 | 18 | def test_group 19 | data = [[1, 2], [3, 4]] 20 | dataset = XGBoost::DMatrix.new(data) 21 | dataset.group = [1, 2] 22 | assert_equal [0, 1], dataset.group 23 | end 24 | 25 | def test_feature_names_and_types 26 | data = [[1, 2], [3, 4]] 27 | label = [1, 2] 28 | dataset = XGBoost::DMatrix.new(data, label: label) 29 | assert_nil dataset.feature_names 30 | assert_nil dataset.feature_types 31 | end 32 | 33 | def test_num_row 34 | assert_equal 300, regression_train.num_row 35 | end 36 | 37 | def test_num_col 38 | assert_equal 4, regression_train.num_col 39 | end 40 | 41 | def test_num_nonmissing 42 | assert_equal 1200, regression_train.num_nonmissing 43 | end 44 | 45 | def test_data_split_mode 46 | assert_equal :row, regression_train.data_split_mode 47 | end 48 | 49 | def test_save_binary 50 | regression_train.save_binary(tempfile) 51 | assert File.exist?(tempfile) 52 | end 53 | 54 | def test_matrix 55 | data = Matrix.build(3, 3) { |row, col| row + col } 56 | label = Vector.elements([4, 5, 6]) 57 | XGBoost::DMatrix.new(data, label: label) 58 | end 59 | 60 | def test_daru 61 | data = Daru::DataFrame.from_csv(data_path) 62 | label = data["y"] 63 | data = data.delete_vector("y") 64 | dataset = XGBoost::DMatrix.new(data, label: label) 65 | names = ["x0", "x1", "x2", "x3"] 66 | assert_equal names, dataset.feature_names 67 | types = ["float", "float", "float", "int"] 68 | assert_equal types, dataset.feature_types 69 | end 70 | 71 | def test_numo 72 | skip if ["jruby", "truffleruby"].include?(RUBY_ENGINE) 73 | 74 | data = Numo::DFloat.new(3, 5).seq 75 | label = Numo::DFloat.new(3).seq 76 | XGBoost::DMatrix.new(data, label: label) 77 | end 78 | 79 | def test_rover 80 | skip if ["jruby", "truffleruby"].include?(RUBY_ENGINE) 81 | 82 | data = Rover.read_csv(data_path) 83 | label = data.delete("y") 84 | dataset = XGBoost::DMatrix.new(data, label: label) 85 | names = ["x0", "x1", "x2", "x3"] 86 | assert_equal names, dataset.feature_names 87 | types = ["float", "float", "float", "int"] 88 | assert_equal types, dataset.feature_types 89 | end 90 | 91 | def test_invalid 92 | data = [[1, 2], [3, 4, 5]] 93 | error = assert_raises(ArgumentError) do 94 | XGBoost::DMatrix.new(data) 95 | end 96 | assert_equal "Rows have different sizes", error.message 97 | end 98 | 99 | def test_copy 100 | regression_train.dup 101 | regression_train.clone 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/xgboost/ffi.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | module FFI 3 | extend ::FFI::Library 4 | 5 | begin 6 | ffi_lib XGBoost.ffi_lib 7 | rescue LoadError => e 8 | if ["/usr/local", "/opt/homebrew"].any? { |v| e.message.include?("Library not loaded: #{v}/opt/libomp/lib/libomp.dylib") } && e.message.include?("Reason: image not found") 9 | raise LoadError, "OpenMP not found. Run `brew install libomp`" 10 | else 11 | raise e 12 | end 13 | end 14 | 15 | # https://github.com/dmlc/xgboost/blob/master/include/xgboost/c_api.h 16 | # keep same order 17 | 18 | # general 19 | attach_function :XGBoostVersion, %i[pointer pointer pointer], :void 20 | attach_function :XGBGetLastError, %i[], :string 21 | 22 | # dmatrix 23 | attach_function :XGDMatrixCreateFromMat, %i[pointer uint64 uint64 float pointer], :int 24 | attach_function :XGDMatrixSetInfoFromInterface, %i[pointer string string], :int 25 | attach_function :XGDMatrixSetStrFeatureInfo, %i[pointer string pointer uint64], :int 26 | attach_function :XGDMatrixGetStrFeatureInfo, %i[pointer string pointer pointer], :int 27 | attach_function :XGDMatrixNumRow, %i[pointer pointer], :int 28 | attach_function :XGDMatrixNumCol, %i[pointer pointer], :int 29 | attach_function :XGDMatrixNumNonMissing, %i[pointer pointer], :int 30 | attach_function :XGDMatrixDataSplitMode, %i[pointer pointer], :int 31 | attach_function :XGDMatrixSliceDMatrix, %i[pointer pointer uint64 pointer], :int 32 | attach_function :XGDMatrixFree, %i[pointer], :int 33 | attach_function :XGDMatrixSaveBinary, %i[pointer string int], :int 34 | attach_function :XGDMatrixSetFloatInfo, %i[pointer string pointer uint64], :int 35 | attach_function :XGDMatrixGetFloatInfo, %i[pointer string pointer pointer], :int 36 | attach_function :XGDMatrixGetUIntInfo, %i[pointer string pointer pointer], :int 37 | 38 | # booster 39 | attach_function :XGBoosterCreate, %i[pointer int pointer], :int 40 | attach_function :XGBoosterUpdateOneIter, %i[pointer int pointer], :int 41 | attach_function :XGBoosterEvalOneIter, %i[pointer int pointer pointer uint64 pointer], :int 42 | attach_function :XGBoosterFree, %i[pointer], :int 43 | attach_function :XGBoosterReset, %i[pointer], :int 44 | attach_function :XGBoosterBoostedRounds, %i[pointer pointer], :int 45 | attach_function :XGBoosterSetParam, %i[pointer string string], :int 46 | attach_function :XGBoosterGetNumFeature, %i[pointer pointer], :int 47 | attach_function :XGBoosterPredict, %i[pointer pointer int int int pointer pointer], :int 48 | attach_function :XGBoosterLoadModel, %i[pointer string], :int 49 | attach_function :XGBoosterSaveModel, %i[pointer string], :int 50 | attach_function :XGBoosterSaveJsonConfig, %i[pointer pointer pointer], :int 51 | attach_function :XGBoosterDumpModelExWithFeatures, %i[pointer int pointer pointer int string pointer pointer], :int 52 | attach_function :XGBoosterGetAttr, %i[pointer string pointer pointer], :int 53 | attach_function :XGBoosterSetAttr, %i[pointer string string], :int 54 | attach_function :XGBoosterGetAttrNames, %i[pointer pointer pointer], :int 55 | attach_function :XGBoosterSetStrFeatureInfo, %i[pointer string pointer uint64], :int 56 | attach_function :XGBoosterGetStrFeatureInfo, %i[pointer string pointer pointer], :int 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/classifier_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class ClassifierTest < Minitest::Test 4 | def test_binary 5 | x_train, y_train, x_test, _ = binary_data 6 | 7 | model = XGBoost::Classifier.new 8 | model.fit(x_train, y_train) 9 | y_pred = model.predict(x_test) 10 | expected = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1] 11 | assert_equal expected, y_pred.first(100) 12 | 13 | y_pred_proba = model.predict_proba(x_test) 14 | expected = [4.673004150390625e-05, 0.9999532699584961] 15 | assert_elements_in_delta expected, y_pred_proba.first 16 | 17 | expected = [0.13950465619564056, 0.25203850865364075, 0.5016216039657593, 0.1068352460861206] 18 | assert_elements_in_delta expected, model.feature_importances 19 | 20 | model.save_model(tempfile) 21 | 22 | model = XGBoost::Classifier.new 23 | model.load_model(tempfile) 24 | assert_equal y_pred, model.predict(x_test) 25 | end 26 | 27 | def test_multiclass 28 | x_train, y_train, x_test, _ = multiclass_data 29 | 30 | model = XGBoost::Classifier.new 31 | model.fit(x_train, y_train) 32 | y_pred = model.predict(x_test) 33 | expected = [1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 2, 2, 1, 1, 1, 0, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1] 34 | assert_equal expected, y_pred.first(100) 35 | 36 | y_pred_proba = model.predict_proba(x_test) 37 | expected = [0.0020083063282072544, 0.9485360383987427, 0.04945564642548561] 38 | assert_elements_in_delta expected, y_pred_proba.first 39 | 40 | expected = [0.160569965839386, 0.33308327198028564, 0.3969796299934387, 0.10936711728572845] 41 | assert_elements_in_delta expected, model.feature_importances 42 | 43 | model.save_model(tempfile) 44 | 45 | model = XGBoost::Classifier.new 46 | model.load_model(tempfile) 47 | assert_equal y_pred, model.predict(x_test) 48 | end 49 | 50 | def test_early_stopping 51 | x_train, y_train, x_test, y_test = multiclass_data 52 | 53 | model = XGBoost::Classifier.new(early_stopping_rounds: 5) 54 | model.fit(x_train, y_train, eval_set: [[x_test, y_test]], verbose: false) 55 | assert_equal 23, model.booster.best_iteration 56 | end 57 | 58 | def test_missing 59 | x_train, y_train, x_test, _ = multiclass_data 60 | 61 | x_train = x_train.map(&:dup) 62 | x_test = x_test.map(&:dup) 63 | [x_train, x_test].each do |xt| 64 | xt.each do |x| 65 | x.size.times do |i| 66 | x[i] = nil if x[i] == 3.7 67 | end 68 | end 69 | end 70 | 71 | model = XGBoost::Classifier.new 72 | model.fit(x_train, y_train) 73 | 74 | y_pred = model.predict(x_test) 75 | expected = [1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 0, 1, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1] 76 | assert_equal expected, y_pred.first(100) 77 | 78 | expected = [0.15650030970573425, 0.33717694878578186, 0.39813780784606934, 0.10818499326705933] 79 | assert_elements_in_delta expected, model.feature_importances 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/xgboost/early_stopping.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class EarlyStopping < TrainingCallback 3 | def initialize( 4 | rounds:, 5 | metric_name: nil, 6 | data_name: nil, 7 | maximize: nil, 8 | save_best: false, 9 | min_delta: 0.0 10 | ) 11 | @data = data_name 12 | @metric_name = metric_name 13 | @rounds = rounds 14 | @save_best = save_best 15 | @maximize = maximize 16 | @stopping_history = {} 17 | @min_delta = min_delta 18 | if @min_delta < 0 19 | raise ArgumentError, "min_delta must be greater or equal to 0." 20 | end 21 | 22 | @current_rounds = 0 23 | @best_scores = {} 24 | @starting_round = 0 25 | super() 26 | end 27 | 28 | def before_training(model) 29 | @starting_round = model.num_boosted_rounds 30 | model 31 | end 32 | 33 | def after_iteration(model, epoch, evals_log) 34 | epoch += @starting_round 35 | msg = "Must have at least 1 validation dataset for early stopping." 36 | if evals_log.keys.length < 1 37 | raise ArgumentError, msg 38 | end 39 | 40 | # Get data name 41 | if @data 42 | data_name = @data 43 | else 44 | # Use the last one as default. 45 | data_name = evals_log.keys[-1] 46 | end 47 | if !evals_log.include?(data_name) 48 | raise ArgumentError, "No dataset named: #{data_name}" 49 | end 50 | 51 | if !data_name.is_a?(String) 52 | raise TypeError, "The name of the dataset should be a string. Got: #{data_name.class.name}" 53 | end 54 | data_log = evals_log[data_name] 55 | 56 | # Get metric name 57 | if @metric_name 58 | metric_name = @metric_name 59 | else 60 | # Use last metric by default. 61 | metric_name = data_log.keys[-1] 62 | end 63 | if !data_log.include?(metric_name) 64 | raise ArgumentError, "No metric named: #{metric_name}" 65 | end 66 | 67 | # The latest score 68 | score = data_log[metric_name][-1] 69 | update_rounds( 70 | score, data_name, metric_name, model, epoch 71 | ) 72 | end 73 | 74 | def after_training(model) 75 | if !@save_best 76 | return model 77 | end 78 | 79 | best_iteration = model.best_iteration 80 | best_score = model.best_score 81 | # model = model[..(best_iteration + 1)] 82 | model.best_iteration = best_iteration 83 | model.best_score = best_score 84 | model 85 | end 86 | 87 | private 88 | 89 | def update_rounds(score, name, metric, model, epoch) 90 | get_s = lambda do |value| 91 | value.is_a?(Array) ? value[0] : value 92 | end 93 | 94 | maximize = lambda do |new_, best| 95 | get_s.(new_) - @min_delta > get_s.(best) 96 | end 97 | 98 | minimize = lambda do |new_, best| 99 | get_s.(best) - @min_delta > get_s.(new_) 100 | end 101 | 102 | improve_op = @maximize ? maximize : minimize 103 | 104 | if @stopping_history.empty? 105 | # First round 106 | @current_rounds = 0 107 | @stopping_history[name] = {} 108 | @stopping_history[name][metric] = [score] 109 | @best_scores[name] = {} 110 | @best_scores[name][metric] = [score] 111 | model.set_attr(best_score: get_s.(score), best_iteration: epoch) 112 | elsif !improve_op.(score, @best_scores[name][metric][-1]) 113 | # Not improved 114 | @stopping_history[name][metric] << score 115 | @current_rounds += 1 116 | else 117 | # Improved 118 | @stopping_history[name][metric] << score 119 | @best_scores[name][metric] << score 120 | record = @stopping_history[name][metric][-1] 121 | model.set_attr(best_score: get_s.(record), best_iteration: epoch) 122 | @current_rounds = 0 123 | end 124 | 125 | if @current_rounds >= @rounds 126 | # Should stop 127 | return true 128 | end 129 | false 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /lib/xgboost/callback_container.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class CallbackContainer 3 | attr_reader :aggregated_cv, :history 4 | 5 | def initialize(callbacks, is_cv: false) 6 | @callbacks = callbacks 7 | callbacks.each do |callback| 8 | unless callback.is_a?(TrainingCallback) 9 | raise TypeError, "callback must be an instance of XGBoost::TrainingCallback" 10 | end 11 | end 12 | 13 | @history = {} 14 | @is_cv = is_cv 15 | end 16 | 17 | def before_training(model) 18 | @callbacks.each do |callback| 19 | model = callback.before_training(model) 20 | if @is_cv 21 | unless model.is_a?(PackedBooster) 22 | raise TypeError, "before_training should return the model" 23 | end 24 | else 25 | unless model.is_a?(Booster) 26 | raise TypeError, "before_training should return the model" 27 | end 28 | end 29 | end 30 | model 31 | end 32 | 33 | def after_training(model) 34 | @callbacks.each do |callback| 35 | model = callback.after_training(model) 36 | if @is_cv 37 | unless model.is_a?(PackedBooster) 38 | raise TypeError, "after_training should return the model" 39 | end 40 | else 41 | unless model.is_a?(Booster) 42 | raise TypeError, "after_training should return the model" 43 | end 44 | end 45 | end 46 | model 47 | end 48 | 49 | def before_iteration(model, epoch, dtrain, evals) 50 | @callbacks.any? do |callback| 51 | callback.before_iteration(model, epoch, @history) 52 | end 53 | end 54 | 55 | def after_iteration(model, epoch, dtrain, evals) 56 | if @is_cv 57 | scores = model.eval_set(epoch) 58 | scores = aggcv(scores) 59 | @aggregated_cv = scores 60 | update_history(scores, epoch) 61 | else 62 | evals ||= [] 63 | evals.each do |_, name| 64 | if name.include?("-") 65 | raise ArgumentError, "Dataset name should not contain `-`" 66 | end 67 | end 68 | score = model.eval_set(evals, epoch) 69 | metric_score = parse_eval_str(score) 70 | update_history(metric_score, epoch) 71 | end 72 | 73 | @callbacks.any? do |callback| 74 | callback.after_iteration(model, epoch, @history) 75 | end 76 | end 77 | 78 | private 79 | 80 | def update_history(score, epoch) 81 | score.each do |d| 82 | name = d[0] 83 | s = d[1] 84 | if @is_cv 85 | std = d[2] 86 | x = [s, std] 87 | else 88 | x = s 89 | end 90 | splited_names = name.split("-") 91 | data_name = splited_names[0] 92 | metric_name = splited_names[1..].join("-") 93 | @history[data_name] ||= {} 94 | data_history = @history[data_name] 95 | data_history[metric_name] ||= [] 96 | metric_history = data_history[metric_name] 97 | metric_history << x 98 | end 99 | end 100 | 101 | # TODO move 102 | def parse_eval_str(result) 103 | splited = result.split[1..] 104 | # split up `test-error:0.1234` 105 | metric_score_str = splited.map { |s| s.split(":") } 106 | # convert to float 107 | metric_score = metric_score_str.map { |n, s| [n, s.to_f] } 108 | metric_score 109 | end 110 | 111 | def aggcv(rlist) 112 | cvmap = {} 113 | rlist.each do |line| 114 | arr = line.split 115 | arr[1..].each_with_index do |it, metric_idx| 116 | k, v = it.split(":") 117 | (cvmap[[metric_idx, k]] ||= []) << v.to_f 118 | end 119 | end 120 | results = [] 121 | cvmap.sort { |x| x[0][0] }.each do |(_, name), s| 122 | mean = mean(s) 123 | std = stdev(s) 124 | results << [name, mean, std] 125 | end 126 | results 127 | end 128 | 129 | def mean(arr) 130 | arr.sum / arr.size.to_f 131 | end 132 | 133 | # don't subtract one from arr.size 134 | def stdev(arr) 135 | m = mean(arr) 136 | sum = 0 137 | arr.each do |v| 138 | sum += (v - m) ** 2 139 | end 140 | Math.sqrt(sum / arr.size) 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XGBoost Ruby 2 | 3 | [XGBoost](https://github.com/dmlc/xgboost) - high performance gradient boosting - for Ruby 4 | 5 | [![Build Status](https://github.com/ankane/xgboost-ruby/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/xgboost-ruby/actions) 6 | 7 | ## Installation 8 | 9 | Add this line to your application’s Gemfile: 10 | 11 | ```ruby 12 | gem "xgb" 13 | ``` 14 | 15 | On Mac, also install OpenMP: 16 | 17 | ```sh 18 | brew install libomp 19 | ``` 20 | 21 | ## Learning API 22 | 23 | Prep your data 24 | 25 | ```ruby 26 | x = [[1, 2], [3, 4], [5, 6], [7, 8]] 27 | y = [1, 2, 3, 4] 28 | ``` 29 | 30 | Train a model 31 | 32 | ```ruby 33 | params = {objective: "reg:squarederror"} 34 | dtrain = XGBoost::DMatrix.new(x, label: y) 35 | booster = XGBoost.train(params, dtrain) 36 | ``` 37 | 38 | Predict 39 | 40 | ```ruby 41 | dtest = XGBoost::DMatrix.new(x) 42 | booster.predict(dtest) 43 | ``` 44 | 45 | Save the model to a file 46 | 47 | ```ruby 48 | booster.save_model("model.json") 49 | ``` 50 | 51 | Load the model from a file 52 | 53 | ```ruby 54 | booster = XGBoost::Booster.new(model_file: "model.json") 55 | ``` 56 | 57 | Get the importance of features 58 | 59 | ```ruby 60 | booster.score 61 | ``` 62 | 63 | Early stopping 64 | 65 | ```ruby 66 | XGBoost.train(params, dtrain, evals: [[dtrain, "train"], [dtest, "eval"]], early_stopping_rounds: 5) 67 | ``` 68 | 69 | CV 70 | 71 | ```ruby 72 | XGBoost.cv(params, dtrain, nfold: 3, verbose_eval: true) 73 | ``` 74 | 75 | Set metadata about a model 76 | 77 | ```ruby 78 | booster["key"] = "value" 79 | booster.attributes 80 | ``` 81 | 82 | ## Scikit-Learn API 83 | 84 | Prep your data 85 | 86 | ```ruby 87 | x = [[1, 2], [3, 4], [5, 6], [7, 8]] 88 | y = [1, 2, 3, 4] 89 | ``` 90 | 91 | Train a model 92 | 93 | ```ruby 94 | model = XGBoost::Regressor.new 95 | model.fit(x, y) 96 | ``` 97 | 98 | > For classification, use `XGBoost::Classifier` 99 | 100 | Predict 101 | 102 | ```ruby 103 | model.predict(x) 104 | ``` 105 | 106 | > For classification, use `predict_proba` for probabilities 107 | 108 | Save the model to a file 109 | 110 | ```ruby 111 | model.save_model("model.json") 112 | ``` 113 | 114 | Load the model from a file 115 | 116 | ```ruby 117 | model.load_model("model.json") 118 | ``` 119 | 120 | Get the importance of features 121 | 122 | ```ruby 123 | model.feature_importances 124 | ``` 125 | 126 | Early stopping 127 | 128 | ```ruby 129 | model = XGBoost::Regressor.new(early_stopping_rounds: 5) 130 | model.fit(x, y, eval_set: [[x_test, y_test]]) 131 | ``` 132 | 133 | ## Data 134 | 135 | Data can be an array of arrays 136 | 137 | ```ruby 138 | [[1, 2, 3], [4, 5, 6]] 139 | ``` 140 | 141 | Or a Numo array 142 | 143 | ```ruby 144 | Numo::NArray.cast([[1, 2, 3], [4, 5, 6]]) 145 | ``` 146 | 147 | Or a Rover data frame 148 | 149 | ```ruby 150 | Rover.read_csv("houses.csv") 151 | ``` 152 | 153 | Or a Daru data frame 154 | 155 | ```ruby 156 | Daru::DataFrame.from_csv("houses.csv") 157 | ``` 158 | 159 | ## Helpful Resources 160 | 161 | - [Parameters](https://xgboost.readthedocs.io/en/latest/parameter.html) 162 | - [Parameter Tuning](https://xgboost.readthedocs.io/en/latest/tutorials/param_tuning.html) 163 | 164 | ## Related Projects 165 | 166 | - [LightGBM](https://github.com/ankane/lightgbm) - LightGBM for Ruby 167 | - [Eps](https://github.com/ankane/eps) - Machine learning for Ruby 168 | 169 | ## Credits 170 | 171 | This library follows the [Python API](https://xgboost.readthedocs.io/en/latest/python/python_api.html), with the `get_` and `set_` prefixes removed from methods to make it more Ruby-like. 172 | 173 | Thanks to the [xgboost](https://github.com/PairOnAir/xgboost-ruby) gem for showing how to use FFI. 174 | 175 | ## History 176 | 177 | View the [changelog](https://github.com/ankane/xgboost-ruby/blob/master/CHANGELOG.md) 178 | 179 | ## Contributing 180 | 181 | Everyone is encouraged to help improve this project. Here are a few ways you can help: 182 | 183 | - [Report bugs](https://github.com/ankane/xgboost-ruby/issues) 184 | - Fix bugs and [submit pull requests](https://github.com/ankane/xgboost-ruby/pulls) 185 | - Write, clarify, or fix documentation 186 | - Suggest or add new features 187 | 188 | To get started with development: 189 | 190 | ```sh 191 | git clone https://github.com/ankane/xgboost-ruby.git 192 | cd xgboost-ruby 193 | bundle install 194 | bundle exec rake vendor:all 195 | bundle exec rake test 196 | ``` 197 | -------------------------------------------------------------------------------- /test/callbacks_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "test_helper" 2 | 3 | class MockCallback < XGBoost::TrainingCallback 4 | attr_reader :before_training_count, :after_training_count, :before_iteration_count, 5 | :after_iteration_count, :before_iteration_args, :history 6 | 7 | def initialize 8 | @before_training_count = 0 9 | @after_training_count = 0 10 | @before_iteration_count = 0 11 | @after_iteration_count = 0 12 | @before_iteration_args = [] 13 | @history = {} 14 | end 15 | 16 | def before_training(model) 17 | @before_training_count += 1 18 | model 19 | end 20 | 21 | def after_training(model) 22 | @after_training_count += 1 23 | model 24 | end 25 | 26 | def before_iteration(model, epoch, evals_log) 27 | @before_iteration_count += 1 28 | @before_iteration_args << {epoch: epoch} 29 | false 30 | end 31 | 32 | def after_iteration(model, epoch, evals_log) 33 | @after_iteration_count += 1 34 | @history = evals_log 35 | false 36 | end 37 | end 38 | 39 | class CallbacksTest < Minitest::Test 40 | def test_callback_raises_when_not_training_callback 41 | error = assert_raises(TypeError) do 42 | XGBoost.train(regression_params, regression_train, callbacks: [Object.new]) 43 | end 44 | assert_equal "callback must be an instance of XGBoost::TrainingCallback", error.message 45 | end 46 | 47 | def test_callback 48 | callback = MockCallback.new 49 | num_boost_round = 10 50 | 51 | XGBoost.train( 52 | regression_params, 53 | regression_train, 54 | num_boost_round: num_boost_round, 55 | callbacks: [callback], 56 | evals: [[regression_train, "train"], [regression_test, "eval"]], 57 | verbose_eval: false 58 | ) 59 | 60 | assert_equal 1, callback.before_training_count 61 | assert_equal 1, callback.after_training_count 62 | assert_equal num_boost_round, callback.before_iteration_count 63 | assert_equal num_boost_round, callback.after_iteration_count 64 | 65 | # Verify arguments 66 | train_rmse = callback.history["train"]["rmse"] 67 | assert_equal num_boost_round, train_rmse.size 68 | train_rmse.each do |value| 69 | assert_in_delta 0.00, value, 1.0 70 | end 71 | eval_rmse = callback.history["eval"]["rmse"] 72 | assert_equal num_boost_round, eval_rmse.size 73 | eval_rmse.each do |value| 74 | assert_in_delta 0.00, value, 1.0 75 | end 76 | 77 | epochs = callback.before_iteration_args.map { |e| e[:epoch] } 78 | assert_equal (0...num_boost_round).to_a, epochs 79 | end 80 | 81 | def test_callback_breaks_on_before_iteration 82 | callback = MockCallback.new 83 | def callback.before_iteration(model, epoch, evals_log) 84 | @before_iteration_count += 1 85 | @before_iteration_args << {epoch: epoch} 86 | epoch.odd? 87 | end 88 | 89 | XGBoost.train( 90 | regression_params, 91 | regression_train, 92 | callbacks: [callback], 93 | evals: [[regression_train, "train"], [regression_test, "eval"]], 94 | verbose_eval: false 95 | ) 96 | 97 | assert_equal 1, callback.before_training_count 98 | assert_equal 1, callback.after_training_count 99 | assert_equal 2, callback.before_iteration_count 100 | assert_equal 1, callback.after_iteration_count 101 | 102 | # Verify arguments 103 | train_rmse = callback.history["train"]["rmse"] 104 | assert_equal 1, train_rmse.size 105 | train_rmse.each do |value| 106 | assert_in_delta 0.00, value, 1.0 107 | end 108 | eval_rmse = callback.history["eval"]["rmse"] 109 | assert_equal 1, eval_rmse.size 110 | eval_rmse.each do |value| 111 | assert_in_delta 0.00, value, 1.0 112 | end 113 | 114 | epochs = callback.before_iteration_args.map { |e| e[:epoch] } 115 | assert_equal (0...2).to_a, epochs 116 | end 117 | 118 | def test_callback_breaks_on_after_iteration 119 | callback = MockCallback.new 120 | def callback.after_iteration(model, epoch, evals_log) 121 | @after_iteration_count += 1 122 | @history = evals_log 123 | epoch >= 7 124 | end 125 | 126 | XGBoost.train( 127 | regression_params, 128 | regression_train, 129 | callbacks: [callback], 130 | evals: [[regression_train, "train"], [regression_test, "eval"]], 131 | verbose_eval: false 132 | ) 133 | 134 | assert_equal 1, callback.before_training_count 135 | assert_equal 1, callback.after_training_count 136 | assert_equal 8, callback.before_iteration_count 137 | assert_equal 8, callback.after_iteration_count 138 | 139 | # Verify arguments 140 | train_rmse = callback.history["train"]["rmse"] 141 | assert_equal 8, train_rmse.size 142 | train_rmse.each do |value| 143 | assert_in_delta 0.00, value, 1.0 144 | end 145 | eval_rmse = callback.history["eval"]["rmse"] 146 | assert_equal 8, eval_rmse.size 147 | eval_rmse.each do |value| 148 | assert_in_delta 0.00, value, 1.0 149 | end 150 | 151 | epochs = callback.before_iteration_args.map { |e| e[:epoch] } 152 | assert_equal (0...8).to_a, epochs 153 | end 154 | 155 | def test_updates_model_before_training 156 | callback = MockCallback.new 157 | def callback.before_training(model) 158 | model["device"] = "cuda:0" 159 | model 160 | end 161 | 162 | model = XGBoost.train(regression_params, regression_train, callbacks: [callback]) 163 | 164 | assert_equal model["device"], "cuda:0" 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /lib/xgboost.rb: -------------------------------------------------------------------------------- 1 | # dependencies 2 | require "ffi" 3 | 4 | # modules 5 | require_relative "xgboost/utils" 6 | require_relative "xgboost/booster" 7 | require_relative "xgboost/callback_container" 8 | require_relative "xgboost/cv_pack" 9 | require_relative "xgboost/dmatrix" 10 | require_relative "xgboost/packed_booster" 11 | require_relative "xgboost/version" 12 | 13 | # callbacks 14 | require_relative "xgboost/training_callback" 15 | require_relative "xgboost/early_stopping" 16 | require_relative "xgboost/evaluation_monitor" 17 | 18 | # scikit-learn API 19 | require_relative "xgboost/model" 20 | require_relative "xgboost/classifier" 21 | require_relative "xgboost/ranker" 22 | require_relative "xgboost/regressor" 23 | 24 | module XGBoost 25 | class Error < StandardError; end 26 | 27 | class << self 28 | attr_accessor :ffi_lib 29 | end 30 | lib_path = 31 | if Gem.win_platform? 32 | "x64-mingw/xgboost.dll" 33 | elsif RbConfig::CONFIG["host_os"] =~ /darwin/i 34 | if RbConfig::CONFIG["host_cpu"] =~ /arm|aarch64/i 35 | "arm64-darwin/libxgboost.dylib" 36 | else 37 | "x86_64-darwin/libxgboost.dylib" 38 | end 39 | elsif RbConfig::CONFIG["host_os"] =~ /linux-musl/i 40 | "x86_64-linux-musl/libxgboost.so" 41 | else 42 | if RbConfig::CONFIG["host_cpu"] =~ /arm|aarch64/i 43 | "aarch64-linux/libxgboost.so" 44 | else 45 | "x86_64-linux/libxgboost.so" 46 | end 47 | end 48 | vendor_lib = File.expand_path("../vendor/#{lib_path}", __dir__) 49 | self.ffi_lib = [vendor_lib] 50 | 51 | # friendlier error message 52 | autoload :FFI, "xgboost/ffi" 53 | 54 | class << self 55 | def train( 56 | params, 57 | dtrain, 58 | num_boost_round: 10, 59 | evals: nil, 60 | maximize: nil, 61 | early_stopping_rounds: nil, 62 | evals_result: nil, 63 | verbose_eval: true, 64 | callbacks: nil 65 | ) 66 | callbacks = callbacks.nil? ? [] : callbacks.dup 67 | evals ||= [] 68 | 69 | bst = Booster.new(params: params, cache: [dtrain] + evals.map { |d| d[0] }) 70 | 71 | if verbose_eval 72 | verbose_eval = verbose_eval == true ? 1 : verbose_eval 73 | callbacks << EvaluationMonitor.new(period: verbose_eval) 74 | end 75 | if early_stopping_rounds 76 | callbacks << EarlyStopping.new(rounds: early_stopping_rounds, maximize: maximize) 77 | end 78 | cb_container = CallbackContainer.new(callbacks) 79 | 80 | bst = cb_container.before_training(bst) 81 | 82 | num_boost_round.times do |i| 83 | break if cb_container.before_iteration(bst, i, dtrain, evals) 84 | bst.update(dtrain, i) 85 | break if cb_container.after_iteration(bst, i, dtrain, evals) 86 | end 87 | 88 | bst = cb_container.after_training(bst) 89 | 90 | if !evals_result.nil? 91 | evals_result.merge!(cb_container.history) 92 | end 93 | 94 | bst.reset 95 | end 96 | 97 | def cv( 98 | params, 99 | dtrain, 100 | num_boost_round: 10, 101 | nfold: 3, 102 | maximize: nil, 103 | early_stopping_rounds: nil, 104 | verbose_eval: nil, 105 | show_stdv: true, 106 | seed: 0, 107 | callbacks: nil, 108 | shuffle: true 109 | ) 110 | results = {} 111 | cvfolds = 112 | mknfold( 113 | dall: dtrain, 114 | param: params, 115 | nfold: nfold, 116 | seed: seed, 117 | shuffle: shuffle 118 | ) 119 | 120 | callbacks = callbacks.nil? ? [] : callbacks.dup 121 | 122 | if verbose_eval 123 | verbose_eval = verbose_eval == true ? 1 : verbose_eval 124 | callbacks << EvaluationMonitor.new(period: verbose_eval, show_stdv: show_stdv) 125 | end 126 | if early_stopping_rounds 127 | callbacks << EarlyStopping.new(rounds: early_stopping_rounds, maximize: maximize) 128 | end 129 | callbacks_container = CallbackContainer.new(callbacks, is_cv: true) 130 | 131 | booster = PackedBooster.new(cvfolds) 132 | callbacks_container.before_training(booster) 133 | 134 | num_boost_round.times do |i| 135 | break if callbacks_container.before_iteration(booster, i, dtrain, nil) 136 | booster.update(i) 137 | 138 | should_break = callbacks_container.after_iteration(booster, i, dtrain, nil) 139 | res = callbacks_container.aggregated_cv 140 | res.each do |key, mean, std| 141 | if !results.include?(key + "-mean") 142 | results[key + "-mean"] = [] 143 | end 144 | if !results.include?(key + "-std") 145 | results[key + "-std"] = [] 146 | end 147 | results[key + "-mean"] << mean 148 | results[key + "-std"] << std 149 | end 150 | 151 | if should_break 152 | results.keys.each do |k| 153 | results[k] = results[k][..booster.best_iteration] 154 | end 155 | break 156 | end 157 | end 158 | 159 | callbacks_container.after_training(booster) 160 | 161 | results 162 | end 163 | 164 | def lib_version 165 | major = ::FFI::MemoryPointer.new(:int) 166 | minor = ::FFI::MemoryPointer.new(:int) 167 | patch = ::FFI::MemoryPointer.new(:int) 168 | FFI.XGBoostVersion(major, minor, patch) 169 | "#{major.read_int}.#{minor.read_int}.#{patch.read_int}" 170 | end 171 | 172 | private 173 | 174 | def mknfold(dall:, param:, nfold:, seed:, shuffle:) 175 | rand_idx = (0...dall.num_row).to_a 176 | rand_idx.shuffle!(random: Random.new(seed)) if shuffle 177 | 178 | kstep = (rand_idx.size / nfold.to_f).ceil 179 | out_idset = rand_idx.each_slice(kstep).to_a[0...nfold] 180 | in_idset = [] 181 | nfold.times do |i| 182 | idx = out_idset.dup 183 | idx.delete_at(i) 184 | in_idset << idx.flatten 185 | end 186 | 187 | ret = [] 188 | nfold.times do |k| 189 | fold_dtrain = dall.slice(in_idset[k]) 190 | fold_dvalid = dall.slice(out_idset[k]) 191 | ret << CVPack.new(fold_dtrain, fold_dvalid, param) 192 | end 193 | ret 194 | end 195 | end 196 | end 197 | -------------------------------------------------------------------------------- /lib/xgboost/dmatrix.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class DMatrix 3 | include Utils 4 | 5 | attr_reader :handle 6 | 7 | def initialize(data, label: nil, weight: nil, missing: Float::NAN) 8 | if data.is_a?(::FFI::AutoPointer) 9 | @handle = data 10 | return 11 | end 12 | 13 | if matrix?(data) 14 | nrow = data.row_count 15 | ncol = data.column_count 16 | flat_data = data.to_a.flatten 17 | elsif daru?(data) 18 | nrow, ncol = data.shape 19 | flat_data = data.map_rows(&:to_a).flatten 20 | feature_names = data.each_vector.map(&:name) 21 | feature_types = 22 | data.each_vector.map(&:db_type).map do |v| 23 | case v 24 | when "INTEGER" 25 | "int" 26 | when "DOUBLE" 27 | "float" 28 | else 29 | raise Error, "Unknown feature type: #{v}" 30 | end 31 | end 32 | elsif numo?(data) 33 | nrow, ncol = data.shape 34 | elsif rover?(data) 35 | nrow, ncol = data.shape 36 | feature_names = data.keys 37 | feature_types = 38 | data.types.map do |_, v| 39 | v = v.to_s 40 | if v.start_with?("int") || v.start_with?("uint") 41 | "int" 42 | elsif v.start_with?("float") 43 | "float" 44 | else 45 | raise Error, "Unknown feature type: #{v}" 46 | end 47 | end 48 | data = data.to_numo 49 | else 50 | nrow = data.count 51 | ncol = data.first.count 52 | if !data.all? { |r| r.size == ncol } 53 | raise ArgumentError, "Rows have different sizes" 54 | end 55 | flat_data = data.flatten 56 | end 57 | 58 | c_data = ::FFI::MemoryPointer.new(:float, nrow * ncol) 59 | if numo?(data) 60 | c_data.write_bytes(data.cast_to(Numo::SFloat).to_string) 61 | else 62 | handle_missing(flat_data, missing) 63 | c_data.write_array_of_float(flat_data) 64 | end 65 | 66 | out = ::FFI::MemoryPointer.new(:pointer) 67 | check_call FFI.XGDMatrixCreateFromMat(c_data, nrow, ncol, missing, out) 68 | @handle = ::FFI::AutoPointer.new(out.read_pointer, FFI.method(:XGDMatrixFree)) 69 | 70 | self.feature_names = feature_names if feature_names 71 | self.feature_types = feature_types if feature_types 72 | 73 | self.label = label if label 74 | self.weight = weight if weight 75 | end 76 | 77 | def save_binary(fname, silent: true) 78 | check_call FFI.XGDMatrixSaveBinary(handle, fname, silent ? 1 : 0) 79 | end 80 | 81 | def label=(label) 82 | set_float_info("label", label) 83 | end 84 | 85 | def weight=(weight) 86 | set_float_info("weight", weight) 87 | end 88 | 89 | def group=(group) 90 | c_data = ::FFI::MemoryPointer.new(:uint32, group.size) 91 | c_data.write_array_of_uint32(group) 92 | interface = { 93 | shape: [group.length], 94 | typestr: "|u4", 95 | data: [c_data.address, false], 96 | version: 3 97 | } 98 | check_call FFI.XGDMatrixSetInfoFromInterface(handle, "group", JSON.generate(interface)) 99 | end 100 | 101 | def label 102 | float_info("label") 103 | end 104 | 105 | def weight 106 | float_info("weight") 107 | end 108 | 109 | def group 110 | uint_info("group_ptr") 111 | end 112 | 113 | def num_row 114 | out = ::FFI::MemoryPointer.new(:uint64) 115 | check_call FFI.XGDMatrixNumRow(handle, out) 116 | out.read_uint64 117 | end 118 | 119 | def num_col 120 | out = ::FFI::MemoryPointer.new(:uint64) 121 | check_call FFI.XGDMatrixNumCol(handle, out) 122 | out.read_uint64 123 | end 124 | 125 | def num_nonmissing 126 | out = ::FFI::MemoryPointer.new(:uint64) 127 | check_call FFI.XGDMatrixNumNonMissing(handle, out) 128 | out.read_uint64 129 | end 130 | 131 | def data_split_mode 132 | out = ::FFI::MemoryPointer.new(:uint64) 133 | check_call FFI.XGDMatrixDataSplitMode(handle, out) 134 | out.read_uint64 == 0 ? :row : :col 135 | end 136 | 137 | def slice(rindex) 138 | idxset = ::FFI::MemoryPointer.new(:int, rindex.count) 139 | idxset.write_array_of_int(rindex) 140 | out = ::FFI::MemoryPointer.new(:pointer) 141 | check_call FFI.XGDMatrixSliceDMatrix(handle, idxset, rindex.size, out) 142 | 143 | handle = ::FFI::AutoPointer.new(out.read_pointer, FFI.method(:XGDMatrixFree)) 144 | DMatrix.new(handle) 145 | end 146 | 147 | def feature_names 148 | length = ::FFI::MemoryPointer.new(:uint64) 149 | sarr = ::FFI::MemoryPointer.new(:pointer) 150 | check_call( 151 | FFI.XGDMatrixGetStrFeatureInfo( 152 | handle, 153 | "feature_name", 154 | length, 155 | sarr 156 | ) 157 | ) 158 | feature_names = from_cstr_to_rbstr(sarr, length) 159 | feature_names.empty? ? nil : feature_names 160 | end 161 | 162 | def feature_names=(feature_names) 163 | if feature_names.nil? 164 | check_call( 165 | FFI.XGDMatrixSetStrFeatureInfo( 166 | handle, "feature_name", nil, 0 167 | ) 168 | ) 169 | return 170 | end 171 | 172 | # validate feature name 173 | feature_names = 174 | validate_feature_info( 175 | feature_names, 176 | num_col, 177 | data_split_mode == :col, 178 | "feature names" 179 | ) 180 | if feature_names.length != feature_names.uniq.length 181 | raise ArgumentError, "feature_names must be unique" 182 | end 183 | 184 | # prohibit the use symbols that may affect parsing. e.g. []< 185 | if !feature_names.all? { |f| f.is_a?(String) && !["[", "]", "<"].any? { |x| f.include?(x) } } 186 | raise ArgumentError, "feature_names must be string, and may not contain [, ] or <" 187 | end 188 | 189 | c_feature_names = array_of_pointers(feature_names.map { |f| string_pointer(f) }) 190 | check_call( 191 | FFI.XGDMatrixSetStrFeatureInfo( 192 | handle, 193 | "feature_name", 194 | c_feature_names, 195 | feature_names.length 196 | ) 197 | ) 198 | end 199 | 200 | def feature_types 201 | length = ::FFI::MemoryPointer.new(:uint64) 202 | sarr = ::FFI::MemoryPointer.new(:pointer) 203 | check_call( 204 | FFI.XGDMatrixGetStrFeatureInfo( 205 | handle, 206 | "feature_type", 207 | length, 208 | sarr 209 | ) 210 | ) 211 | res = from_cstr_to_rbstr(sarr, length) 212 | res.empty? ? nil : res 213 | end 214 | 215 | def feature_types=(feature_types) 216 | if feature_types.nil? 217 | check_call( 218 | FFI.XGDMatrixSetStrFeatureInfo( 219 | handle, "feature_type", nil, 0 220 | ) 221 | ) 222 | return 223 | end 224 | 225 | feature_types = 226 | validate_feature_info( 227 | feature_types, 228 | num_col, 229 | data_split_mode == :col, 230 | "feature types" 231 | ) 232 | 233 | c_feature_types = array_of_pointers(feature_types.map { |f| string_pointer(f) }) 234 | check_call( 235 | FFI.XGDMatrixSetStrFeatureInfo( 236 | handle, 237 | "feature_type", 238 | c_feature_types, 239 | feature_types.length 240 | ) 241 | ) 242 | end 243 | 244 | private 245 | 246 | def set_float_info(field, data) 247 | data = data.to_a unless data.is_a?(Array) 248 | c_data = ::FFI::MemoryPointer.new(:float, data.size) 249 | c_data.write_array_of_float(data) 250 | check_call FFI.XGDMatrixSetFloatInfo(handle, field.to_s, c_data, data.size) 251 | end 252 | 253 | def float_info(field) 254 | num_row ||= num_row() 255 | out_len = ::FFI::MemoryPointer.new(:uint64) 256 | out_dptr = ::FFI::MemoryPointer.new(:float, num_row) 257 | check_call FFI.XGDMatrixGetFloatInfo(handle, field, out_len, out_dptr) 258 | out_dptr.read_pointer.null? ? nil : out_dptr.read_pointer.read_array_of_float(num_row) 259 | end 260 | 261 | def uint_info(field) 262 | num_row ||= num_row() 263 | out_len = ::FFI::MemoryPointer.new(:uint64) 264 | out_dptr = ::FFI::MemoryPointer.new(:uint32, num_row) 265 | check_call FFI.XGDMatrixGetUIntInfo(handle, field, out_len, out_dptr) 266 | out_dptr.read_pointer.null? ? nil : out_dptr.read_pointer.read_array_of_uint32(num_row) 267 | end 268 | 269 | def validate_feature_info(feature_info, n_features, is_column_split, name) 270 | if !feature_info.is_a?(Array) 271 | raise TypeError, "Expecting an array of strings for #{name}, got: #{feature_info.class.name}" 272 | end 273 | if feature_info.length != n_features && n_features != 0 && !is_column_split 274 | msg = ( 275 | "#{name} must have the same length as the number of data columns, " + 276 | "expected #{n_features}, got #{feature_info.length}" 277 | ) 278 | raise ArgumentError, msg 279 | end 280 | feature_info 281 | end 282 | 283 | def matrix?(data) 284 | defined?(Matrix) && data.is_a?(Matrix) 285 | end 286 | 287 | def daru?(data) 288 | defined?(Daru::DataFrame) && data.is_a?(Daru::DataFrame) 289 | end 290 | 291 | def numo?(data) 292 | defined?(Numo::NArray) && data.is_a?(Numo::NArray) 293 | end 294 | 295 | def rover?(data) 296 | defined?(Rover::DataFrame) && data.is_a?(Rover::DataFrame) 297 | end 298 | 299 | def handle_missing(data, missing) 300 | data.map! { |v| v.nil? ? missing : v } 301 | end 302 | end 303 | end 304 | -------------------------------------------------------------------------------- /lib/xgboost/booster.rb: -------------------------------------------------------------------------------- 1 | module XGBoost 2 | class Booster 3 | include Utils 4 | 5 | def initialize(params: nil, cache: nil, model_file: nil) 6 | cache ||= [] 7 | cache.each do |d| 8 | if !d.is_a?(DMatrix) 9 | raise TypeError, "invalid cache item: #{d.class.name}" 10 | end 11 | end 12 | 13 | dmats = array_of_pointers(cache.map { |d| d.handle }) 14 | out = ::FFI::MemoryPointer.new(:pointer) 15 | check_call FFI.XGBoosterCreate(dmats, cache.length, out) 16 | @handle = ::FFI::AutoPointer.new(out.read_pointer, FFI.method(:XGBoosterFree)) 17 | 18 | cache.each do |d| 19 | assign_dmatrix_features(d) 20 | end 21 | 22 | if model_file 23 | check_call FFI.XGBoosterLoadModel(handle, model_file) 24 | end 25 | 26 | set_param(params) 27 | end 28 | 29 | def [](key_name) 30 | if key_name.is_a?(String) 31 | return attr(key_name) 32 | end 33 | 34 | # TODO slice 35 | 36 | raise TypeError, "expected string" 37 | end 38 | 39 | def []=(key_name, raw_value) 40 | set_attr(**{key_name => raw_value}) 41 | end 42 | 43 | def save_config 44 | length = ::FFI::MemoryPointer.new(:uint64) 45 | json_string = ::FFI::MemoryPointer.new(:pointer) 46 | check_call FFI.XGBoosterSaveJsonConfig(handle, length, json_string) 47 | json_string.read_pointer.read_string(length.read_uint64).force_encoding(Encoding::UTF_8) 48 | end 49 | 50 | def reset 51 | check_call FFI.XGBoosterReset(handle) 52 | self 53 | end 54 | 55 | def attr(key) 56 | ret = ::FFI::MemoryPointer.new(:pointer) 57 | success = ::FFI::MemoryPointer.new(:int) 58 | check_call FFI.XGBoosterGetAttr(handle, key.to_s, ret, success) 59 | success.read_int != 0 ? ret.read_pointer.read_string : nil 60 | end 61 | 62 | def attributes 63 | length = ::FFI::MemoryPointer.new(:uint64) 64 | sarr = ::FFI::MemoryPointer.new(:pointer) 65 | check_call FFI.XGBoosterGetAttrNames(handle, length, sarr) 66 | attr_names = from_cstr_to_rbstr(sarr, length) 67 | attr_names.to_h { |n| [n, attr(n)] } 68 | end 69 | 70 | def set_attr(**kwargs) 71 | kwargs.each do |key, value| 72 | check_call FFI.XGBoosterSetAttr(handle, key.to_s, value&.to_s) 73 | end 74 | end 75 | 76 | def feature_types 77 | get_feature_info("feature_type") 78 | end 79 | 80 | def feature_types=(features) 81 | set_feature_info(features, "feature_type") 82 | end 83 | 84 | def feature_names 85 | get_feature_info("feature_name") 86 | end 87 | 88 | def feature_names=(features) 89 | set_feature_info(features, "feature_name") 90 | end 91 | 92 | def set_param(params, value = nil) 93 | if params.is_a?(Enumerable) 94 | params.each do |k, v| 95 | check_call FFI.XGBoosterSetParam(handle, k.to_s, v.to_s) 96 | end 97 | else 98 | check_call FFI.XGBoosterSetParam(handle, params.to_s, value.to_s) 99 | end 100 | end 101 | 102 | def update(dtrain, iteration) 103 | check_call FFI.XGBoosterUpdateOneIter(handle, iteration, dtrain.handle) 104 | end 105 | 106 | def eval_set(evals, iteration) 107 | dmats = array_of_pointers(evals.map { |v| v[0].handle }) 108 | evnames = array_of_pointers(evals.map { |v| string_pointer(v[1]) }) 109 | 110 | out_result = ::FFI::MemoryPointer.new(:pointer) 111 | 112 | check_call FFI.XGBoosterEvalOneIter(handle, iteration, dmats, evnames, evals.size, out_result) 113 | 114 | out_result.read_pointer.read_string 115 | end 116 | 117 | def predict(data, ntree_limit: nil) 118 | ntree_limit ||= 0 119 | out_len = ::FFI::MemoryPointer.new(:uint64) 120 | out_result = ::FFI::MemoryPointer.new(:pointer) 121 | check_call FFI.XGBoosterPredict(handle, data.handle, 0, ntree_limit, 0, out_len, out_result) 122 | out = out_result.read_pointer.read_array_of_float(out_len.read_uint64) 123 | num_class = out.size / data.num_row 124 | out = out.each_slice(num_class).to_a if num_class > 1 125 | out 126 | end 127 | 128 | def save_model(fname) 129 | check_call FFI.XGBoosterSaveModel(handle, fname) 130 | end 131 | 132 | def best_iteration 133 | attr(:best_iteration)&.to_i 134 | end 135 | 136 | def best_iteration=(iteration) 137 | set_attr(best_iteration: iteration) 138 | end 139 | 140 | def best_score 141 | attr(:best_score)&.to_f 142 | end 143 | 144 | def best_score=(score) 145 | set_attr(best_score: score) 146 | end 147 | 148 | def num_boosted_rounds 149 | rounds = ::FFI::MemoryPointer.new(:int) 150 | check_call FFI.XGBoosterBoostedRounds(handle, rounds) 151 | rounds.read_int 152 | end 153 | 154 | def num_features 155 | features = ::FFI::MemoryPointer.new(:uint64) 156 | check_call FFI.XGBoosterGetNumFeature(handle, features) 157 | features.read_uint64 158 | end 159 | 160 | def dump_model(fout, fmap: "", with_stats: false, dump_format: "text") 161 | ret = dump(fmap: fmap, with_stats: with_stats, dump_format: dump_format) 162 | File.open(fout, "wb") do |f| 163 | if dump_format == "json" 164 | f.print("[\n") 165 | ret.each_with_index do |r, i| 166 | f.print(r) 167 | f.print(",\n") if i < ret.size - 1 168 | end 169 | f.print("\n]") 170 | else 171 | ret.each_with_index do |r, i| 172 | f.print("booster[#{i}]:\n") 173 | f.print(r) 174 | end 175 | end 176 | end 177 | end 178 | 179 | # returns an array of strings 180 | def dump(fmap: "", with_stats: false, dump_format: "text") 181 | out_len = ::FFI::MemoryPointer.new(:uint64) 182 | out_result = ::FFI::MemoryPointer.new(:pointer) 183 | 184 | names = feature_names || [] 185 | fnames = array_of_pointers(names.map { |fname| string_pointer(fname) }) 186 | ftypes = array_of_pointers(feature_types&.map { |v| string_pointer(v) } || Array.new(names.size, string_pointer("float"))) 187 | 188 | check_call FFI.XGBoosterDumpModelExWithFeatures(handle, names.size, fnames, ftypes, with_stats ? 1 : 0, dump_format, out_len, out_result) 189 | 190 | out_result.read_pointer.get_array_of_string(0, out_len.read_uint64) 191 | end 192 | 193 | def fscore(fmap: "") 194 | # always weight 195 | score(fmap: fmap, importance_type: "weight") 196 | end 197 | 198 | def score(fmap: "", importance_type: "weight") 199 | if importance_type == "weight" 200 | trees = dump(fmap: fmap, with_stats: false) 201 | fmap = {} 202 | trees.each do |tree| 203 | tree.split("\n").each do |line| 204 | arr = line.split("[") 205 | next if arr.size == 1 206 | 207 | fid = arr[1].split("]")[0].split("<")[0] 208 | fmap[fid] ||= 0 209 | fmap[fid] += 1 210 | end 211 | end 212 | fmap 213 | else 214 | average_over_splits = true 215 | if importance_type == "total_gain" 216 | importance_type = "gain" 217 | average_over_splits = false 218 | elsif importance_type == "total_cover" 219 | importance_type = "cover" 220 | average_over_splits = false 221 | end 222 | 223 | trees = dump(fmap: fmap, with_stats: true) 224 | 225 | importance_type += "=" 226 | fmap = {} 227 | gmap = {} 228 | trees.each do |tree| 229 | tree.split("\n").each do |line| 230 | arr = line.split("[") 231 | next if arr.size == 1 232 | 233 | fid = arr[1].split("]") 234 | 235 | g = fid[1].split(importance_type)[1].split(",")[0].to_f 236 | 237 | fid = fid[0].split("<")[0] 238 | 239 | fmap[fid] ||= 0 240 | gmap[fid] ||= 0 241 | 242 | fmap[fid] += 1 243 | gmap[fid] += g 244 | end 245 | end 246 | 247 | if average_over_splits 248 | gmap.each_key do |fid| 249 | gmap[fid] = gmap[fid] / fmap[fid] 250 | end 251 | end 252 | 253 | gmap 254 | end 255 | end 256 | 257 | private 258 | 259 | def handle 260 | @handle 261 | end 262 | 263 | def assign_dmatrix_features(data) 264 | if data.num_row == 0 265 | return 266 | end 267 | 268 | fn = data.feature_names 269 | ft = data.feature_types 270 | 271 | if feature_names.nil? 272 | self.feature_names = fn 273 | end 274 | if feature_types.nil? 275 | self.feature_types = ft 276 | end 277 | end 278 | 279 | def get_feature_info(field) 280 | length = ::FFI::MemoryPointer.new(:uint64) 281 | sarr = ::FFI::MemoryPointer.new(:pointer) 282 | if @handle.nil? 283 | return nil 284 | end 285 | check_call( 286 | FFI.XGBoosterGetStrFeatureInfo( 287 | handle, 288 | field, 289 | length, 290 | sarr 291 | ) 292 | ) 293 | feature_info = from_cstr_to_rbstr(sarr, length) 294 | !feature_info.empty? ? feature_info : nil 295 | end 296 | 297 | def set_feature_info(features, field) 298 | if !features.nil? 299 | if !features.is_a?(Array) 300 | raise TypeError, "features must be an array" 301 | end 302 | c_feature_info = array_of_pointers(features.map { |f| string_pointer(f) }) 303 | check_call( 304 | FFI.XGBoosterSetStrFeatureInfo( 305 | handle, 306 | field, 307 | c_feature_info, 308 | features.length 309 | ) 310 | ) 311 | else 312 | check_call( 313 | FFI.XGBoosterSetStrFeatureInfo( 314 | handle, field, nil, 0 315 | ) 316 | ) 317 | end 318 | end 319 | end 320 | end 321 | -------------------------------------------------------------------------------- /test/support/data.csv: -------------------------------------------------------------------------------- 1 | x0,x1,x2,x3,y 2 | 3.7,1.2,7.2,9,1 3 | 7.5,0.5,7.9,0,1 4 | 1.6,0.1,7.6,7,1 5 | 0.6,2.5,5.0,4,0 6 | 1.8,8.4,1.1,2,1 7 | 6.8,8.7,8.7,9,2 8 | 9.4,9.6,8.6,9,2 9 | 0.9,0.7,6.3,6,1 10 | 5.7,0.1,0.0,1,0 11 | 0.8,8.8,1.3,3,1 12 | 4.7,7.2,3.0,7,1 13 | 0.3,7.0,2.1,1,1 14 | 5.7,0.3,6.8,8,1 15 | 4.3,7.6,2.6,4,1 16 | 8.0,4.1,8.2,0,1 17 | 6.8,2.5,9.8,7,2 18 | 0.7,2.6,2.5,8,0 19 | 2.2,0.9,6.7,7,1 20 | 2.3,2.7,3.7,9,1 21 | 8.3,3.8,0.8,0,0 22 | 3.4,1.0,2.3,7,0 23 | 2.5,7.1,9.2,0,1 24 | 8.8,2.3,5.5,1,1 25 | 7.7,0.3,0.0,8,0 26 | 7.7,0.6,5.2,5,1 27 | 7.0,0.2,7.6,5,1 28 | 7.5,0.7,7.7,8,1 29 | 7.5,7.6,4.3,4,1 30 | 3.0,3.6,0.7,4,0 31 | 5.7,8.2,9.6,7,2 32 | 8.1,0.7,2.4,4,0 33 | 3.2,1.2,6.5,9,1 34 | 2.4,8.2,9.7,2,2 35 | 9.2,9.8,1.0,6,1 36 | 9.6,8.2,8.6,6,2 37 | 6.6,7.1,4.8,6,1 38 | 1.5,0.5,1.7,4,0 39 | 4.8,2.2,1.3,1,0 40 | 9.7,5.3,8.4,0,1 41 | 5.5,6.1,5.6,9,1 42 | 2.1,9.6,8.3,9,2 43 | 1.4,1.3,8.4,4,1 44 | 4.3,0.6,7.7,8,1 45 | 5.9,1.5,2.4,9,1 46 | 6.6,7.1,5.3,5,1 47 | 3.6,2.1,4.0,1,0 48 | 4.7,7.7,4.0,7,1 49 | 1.6,2.8,4.5,3,1 50 | 6.6,7.8,4.6,0,1 51 | 2.9,6.3,7.5,3,1 52 | 5.3,9.3,3.3,2,1 53 | 8.4,8.3,4.8,6,2 54 | 3.2,2.8,5.5,2,1 55 | 3.1,2.8,9.4,8,1 56 | 9.9,3.2,0.8,4,0 57 | 7.7,5.0,7.9,9,2 58 | 6.4,8.3,2.4,1,1 59 | 2.0,4.4,1.5,3,0 60 | 1.9,2.6,8.6,7,1 61 | 9.9,5.3,4.7,3,1 62 | 3.4,3.2,1.9,3,0 63 | 2.4,8.3,9.4,6,2 64 | 4.7,0.5,7.9,1,1 65 | 8.7,3.2,4.2,2,1 66 | 8.8,9.8,3.0,1,1 67 | 6.8,6.4,6.0,4,1 68 | 7.8,1.7,3.9,3,1 69 | 8.1,2.8,2.2,6,1 70 | 4.1,7.4,7.7,6,1 71 | 2.5,4.8,5.0,9,1 72 | 5.4,0.0,8.6,7,1 73 | 1.6,1.9,0.9,0,0 74 | 9.2,5.1,1.0,4,1 75 | 2.3,1.4,6.3,5,1 76 | 4.6,0.3,5.6,8,1 77 | 8.0,4.6,5.4,7,1 78 | 1.4,7.7,1.5,9,1 79 | 5.3,8.4,5.8,5,1 80 | 2.9,4.4,3.7,6,1 81 | 9.1,5.4,7.6,8,1 82 | 5.9,2.6,7.6,7,1 83 | 8.7,3.9,4.3,2,1 84 | 3.8,9.1,6.9,2,2 85 | 3.3,4.3,2.6,8,1 86 | 6.9,7.3,5.2,9,1 87 | 2.7,4.3,0.1,2,0 88 | 9.4,9.6,2.6,4,1 89 | 3.0,6.4,2.2,2,0 90 | 5.2,0.3,7.0,7,1 91 | 8.3,4.8,6.1,6,1 92 | 1.3,3.6,2.3,6,0 93 | 6.6,9.2,9.5,5,2 94 | 0.9,4.1,5.7,8,1 95 | 9.3,7.6,8.2,9,2 96 | 3.6,1.6,3.0,3,0 97 | 8.4,5.7,3.5,9,1 98 | 8.1,0.2,8.5,4,1 99 | 4.6,8.0,2.7,8,1 100 | 2.8,0.2,9.9,9,1 101 | 3.6,4.0,1.8,1,0 102 | 3.8,6.3,4.6,8,1 103 | 8.9,0.2,9.8,8,1 104 | 2.1,5.8,1.6,5,0 105 | 1.1,2.2,0.6,0,0 106 | 1.4,8.7,7.5,3,1 107 | 6.3,3.2,9.1,1,1 108 | 0.0,2.2,9.7,6,1 109 | 3.6,1.8,2.7,5,0 110 | 5.2,9.4,5.0,7,1 111 | 8.5,1.6,2.9,8,1 112 | 3.0,0.8,4.0,0,0 113 | 3.0,2.3,6.6,0,1 114 | 1.2,1.7,3.3,9,0 115 | 7.5,0.5,8.1,5,1 116 | 4.2,8.6,5.2,9,1 117 | 5.6,7.8,8.7,5,2 118 | 8.1,1.0,7.2,0,1 119 | 1.9,1.2,2.5,0,0 120 | 0.4,8.8,2.7,2,1 121 | 6.8,0.3,5.8,2,1 122 | 7.6,9.6,9.6,4,2 123 | 7.4,1.2,1.8,5,0 124 | 1.6,9.5,9.6,3,2 125 | 4.4,2.4,6.2,5,1 126 | 1.9,7.7,3.3,4,1 127 | 3.9,6.7,3.4,2,1 128 | 4.2,7.8,8.2,6,2 129 | 3.8,4.8,8.1,5,1 130 | 9.1,0.8,4.0,5,1 131 | 0.5,0.7,7.8,5,1 132 | 7.8,3.3,2.7,3,1 133 | 7.7,3.0,4.3,9,1 134 | 6.7,0.3,6.7,6,1 135 | 9.7,6.3,9.9,0,2 136 | 2.8,3.7,6.4,5,1 137 | 9.3,1.8,7.8,7,1 138 | 7.0,9.2,5.2,0,1 139 | 4.6,6.3,9.3,2,1 140 | 3.6,2.9,3.9,6,1 141 | 2.9,5.5,9.0,1,1 142 | 7.1,9.1,0.7,3,1 143 | 5.6,5.1,3.2,6,1 144 | 5.1,9.6,3.0,6,1 145 | 0.5,9.1,9.5,9,2 146 | 2.2,7.6,2.2,6,1 147 | 1.8,9.3,2.9,2,1 148 | 5.2,3.1,4.9,2,1 149 | 8.3,9.3,4.5,3,1 150 | 8.6,1.5,7.1,0,1 151 | 2.8,8.3,2.7,3,1 152 | 7.9,7.0,8.8,6,2 153 | 1.1,6.5,9.3,3,1 154 | 9.8,8.6,4.5,3,2 155 | 6.3,4.2,2.2,7,1 156 | 8.2,4.0,4.4,0,1 157 | 1.7,8.8,0.0,0,0 158 | 9.7,8.2,7.1,4,2 159 | 1.4,3.6,2.6,0,0 160 | 0.1,4.8,8.8,5,1 161 | 2.2,3.4,9.4,5,1 162 | 6.8,6.7,4.8,6,1 163 | 1.8,9.7,1.1,9,1 164 | 5.2,7.6,6.8,0,1 165 | 8.9,4.0,0.7,7,1 166 | 3.3,5.8,0.7,2,0 167 | 0.4,0.5,6.1,6,1 168 | 5.0,7.6,4.5,0,1 169 | 2.1,9.2,7.1,9,2 170 | 9.7,0.1,1.3,4,0 171 | 3.8,8.5,2.7,6,1 172 | 8.4,3.3,0.1,5,0 173 | 3.8,2.8,7.5,2,1 174 | 9.9,3.1,5.1,2,1 175 | 6.7,3.2,7.4,0,1 176 | 9.7,0.5,0.0,5,0 177 | 7.2,4.3,2.4,0,1 178 | 9.6,9.6,4.1,8,2 179 | 2.1,2.7,4.6,3,1 180 | 6.2,6.8,6.2,0,1 181 | 4.5,8.8,4.2,9,1 182 | 2.5,3.2,3.3,6,1 183 | 0.6,6.5,5.1,7,1 184 | 1.3,0.3,2.8,2,0 185 | 6.1,8.3,9.6,2,2 186 | 3.4,3.0,5.7,0,1 187 | 4.9,4.6,8.3,9,1 188 | 2.0,4.0,5.0,8,1 189 | 0.4,0.9,0.0,5,0 190 | 7.4,2.1,3.3,7,1 191 | 3.8,3.7,4.3,7,1 192 | 3.3,9.5,4.1,7,1 193 | 6.9,7.0,2.3,6,1 194 | 3.9,3.1,3.7,6,1 195 | 5.6,8.7,2.8,4,1 196 | 6.6,1.0,8.4,0,1 197 | 1.6,3.0,0.3,5,0 198 | 6.9,4.9,7.4,1,1 199 | 7.2,6.8,3.9,0,1 200 | 9.7,0.7,3.3,5,1 201 | 3.6,1.2,5.3,6,1 202 | 2.6,8.2,9.9,9,2 203 | 9.9,2.5,0.1,9,1 204 | 5.5,5.5,6.4,8,1 205 | 6.0,7.0,4.9,2,1 206 | 3.2,9.0,2.0,4,1 207 | 3.8,6.5,8.0,9,1 208 | 5.2,8.9,1.9,0,1 209 | 4.2,4.7,2.1,1,1 210 | 0.1,8.9,3.8,0,1 211 | 1.8,7.7,7.7,8,1 212 | 9.0,8.3,3.9,2,1 213 | 1.5,3.7,9.6,4,1 214 | 9.8,0.1,3.2,5,1 215 | 0.7,5.0,5.0,0,1 216 | 4.3,4.1,6.9,1,1 217 | 9.7,5.1,3.0,2,1 218 | 4.9,8.1,8.2,7,2 219 | 2.1,1.8,4.6,9,1 220 | 0.4,7.7,6.3,7,1 221 | 9.5,5.9,6.4,2,1 222 | 3.1,0.1,8.4,9,1 223 | 6.8,5.4,2.5,3,1 224 | 2.7,5.6,9.2,8,1 225 | 5.5,7.6,4.6,1,1 226 | 1.1,0.8,6.1,7,1 227 | 8.8,8.9,8.2,2,2 228 | 0.5,4.2,4.6,5,1 229 | 5.1,7.3,4.2,2,1 230 | 2.3,4.5,9.5,4,1 231 | 3.3,5.4,1.4,9,1 232 | 6.1,4.0,7.2,1,1 233 | 2.2,0.2,8.6,1,1 234 | 7.2,5.1,7.1,0,1 235 | 8.7,7.7,6.2,0,1 236 | 0.0,4.2,3.9,3,0 237 | 5.7,4.3,5.3,2,1 238 | 9.7,7.5,5.9,2,1 239 | 7.4,9.1,0.5,3,1 240 | 3.1,1.1,0.3,6,0 241 | 0.1,1.3,5.9,8,1 242 | 8.1,0.8,3.8,4,1 243 | 7.0,7.6,7.3,5,2 244 | 7.9,8.4,5.9,7,1 245 | 6.6,3.2,4.6,2,1 246 | 4.4,6.4,3.7,9,1 247 | 9.0,8.4,1.5,1,1 248 | 5.2,6.9,6.6,9,2 249 | 2.8,1.9,1.7,5,0 250 | 7.9,1.0,2.9,1,1 251 | 8.1,4.6,0.7,1,1 252 | 8.6,6.1,6.4,7,2 253 | 2.9,5.5,2.8,4,1 254 | 9.9,4.3,9.8,7,2 255 | 0.5,5.0,9.2,4,1 256 | 3.4,9.8,8.3,5,2 257 | 8.9,5.4,0.4,9,1 258 | 3.2,5.0,5.1,7,1 259 | 8.9,4.3,8.8,0,1 260 | 8.6,5.8,8.8,3,2 261 | 2.2,1.0,3.1,4,0 262 | 0.9,0.6,3.0,3,0 263 | 1.8,1.2,7.3,1,1 264 | 8.5,5.3,4.2,6,1 265 | 0.9,3.6,8.7,8,1 266 | 5.3,8.7,1.8,5,1 267 | 6.7,1.4,2.0,5,1 268 | 0.9,4.2,6.6,4,1 269 | 1.6,5.8,7.7,3,1 270 | 7.5,2.8,8.5,2,1 271 | 4.4,9.2,8.2,8,2 272 | 3.0,6.4,7.7,2,1 273 | 3.8,1.1,5.6,4,1 274 | 8.3,7.6,6.8,9,2 275 | 9.9,6.9,3.1,2,1 276 | 3.2,7.1,6.0,8,1 277 | 2.7,9.7,9.3,4,2 278 | 3.8,6.0,9.7,6,2 279 | 0.9,6.7,7.8,4,1 280 | 3.5,5.5,2.8,8,1 281 | 5.1,0.4,7.9,4,1 282 | 7.6,5.2,3.8,3,1 283 | 8.0,7.4,7.0,4,2 284 | 5.7,3.0,9.9,0,1 285 | 3.8,1.8,9.5,9,1 286 | 9.5,5.7,4.8,1,1 287 | 5.2,2.2,6.4,6,1 288 | 6.1,6.0,9.6,0,1 289 | 2.6,5.0,2.0,1,0 290 | 7.7,1.8,6.0,2,1 291 | 5.9,6.2,3.8,7,1 292 | 6.8,6.9,6.1,7,1 293 | 6.6,1.9,3.6,1,1 294 | 2.1,7.7,2.6,1,1 295 | 3.2,0.8,0.8,4,0 296 | 6.8,8.2,5.4,8,2 297 | 8.5,9.3,7.4,9,2 298 | 2.3,9.4,9.0,8,2 299 | 2.4,4.7,6.8,9,1 300 | 1.3,0.6,0.2,3,0 301 | 9.3,0.4,2.8,3,1 302 | 8.2,7.7,4.6,9,1 303 | 0.6,9.1,8.0,4,1 304 | 2.3,6.3,9.9,7,2 305 | 0.6,2.1,6.4,2,1 306 | 9.0,0.9,9.2,3,1 307 | 8.5,3.3,7.1,9,1 308 | 0.8,5.1,3.3,7,1 309 | 9.9,6.1,0.7,9,1 310 | 2.7,5.1,4.0,7,1 311 | 4.5,9.4,0.9,8,1 312 | 4.6,5.8,2.1,6,1 313 | 0.0,1.9,1.9,5,0 314 | 2.7,9.7,2.1,2,1 315 | 6.7,3.5,9.5,7,2 316 | 2.2,5.6,1.0,8,0 317 | 3.1,1.2,8.2,7,1 318 | 8.7,0.8,6.1,7,1 319 | 3.4,9.4,0.3,7,1 320 | 6.8,1.5,2.4,9,1 321 | 2.6,9.8,4.7,7,1 322 | 3.5,1.6,8.7,7,1 323 | 5.7,2.4,5.5,1,1 324 | 5.2,2.4,1.5,3,0 325 | 7.3,4.6,2.0,2,1 326 | 9.4,1.2,2.5,0,0 327 | 9.7,9.8,7.7,8,2 328 | 5.4,4.7,7.7,7,1 329 | 4.0,5.9,9.5,6,1 330 | 7.9,8.9,3.1,8,1 331 | 3.3,3.8,9.2,4,1 332 | 1.4,0.9,6.7,1,1 333 | 7.9,0.3,4.7,6,1 334 | 4.2,0.5,2.0,9,0 335 | 5.9,5.4,7.7,8,1 336 | 5.8,7.9,3.6,0,1 337 | 5.8,5.1,6.3,9,1 338 | 8.3,8.8,8.2,8,2 339 | 5.5,7.8,7.0,4,2 340 | 2.7,2.3,5.3,5,1 341 | 6.8,2.3,4.6,7,1 342 | 6.6,7.7,2.6,8,1 343 | 1.7,8.7,0.1,5,1 344 | 1.7,5.6,3.3,2,1 345 | 0.2,9.6,9.0,3,1 346 | 2.6,2.6,5.2,7,1 347 | 0.7,6.2,9.9,7,1 348 | 7.0,6.9,5.5,2,1 349 | 8.7,8.5,7.4,5,2 350 | 1.4,5.6,8.7,1,1 351 | 8.4,7.5,7.6,2,1 352 | 6.2,4.5,6.5,4,1 353 | 2.8,8.7,3.8,4,1 354 | 9.7,5.1,5.9,0,1 355 | 1.4,9.7,5.2,0,1 356 | 6.4,3.0,8.2,4,1 357 | 1.0,4.5,8.8,7,1 358 | 9.9,5.9,9.7,0,2 359 | 0.5,9.0,7.6,7,1 360 | 4.2,2.0,8.8,4,1 361 | 1.8,9.2,4.9,3,1 362 | 1.5,1.7,6.1,7,1 363 | 6.4,5.5,0.2,3,1 364 | 0.4,1.6,7.7,4,1 365 | 3.0,5.2,8.4,4,1 366 | 9.8,5.9,3.1,3,1 367 | 3.2,5.0,5.1,5,1 368 | 3.3,0.0,0.9,3,0 369 | 8.8,6.0,3.8,7,1 370 | 6.2,5.6,9.6,4,2 371 | 2.4,4.3,5.1,1,1 372 | 1.8,1.0,6.1,7,1 373 | 0.6,0.7,7.9,3,1 374 | 0.0,0.3,5.1,3,0 375 | 8.1,5.9,6.6,0,1 376 | 1.9,4.4,8.7,5,1 377 | 0.0,6.3,9.4,4,1 378 | 0.1,8.4,6.0,6,1 379 | 1.4,2.4,4.6,8,1 380 | 3.2,8.0,7.0,6,1 381 | 2.4,7.7,8.9,3,1 382 | 5.7,3.1,3.9,1,1 383 | 9.3,4.8,1.3,3,1 384 | 1.5,9.7,4.1,3,1 385 | 2.5,5.1,3.4,6,1 386 | 3.3,9.9,6.1,7,1 387 | 3.2,0.3,6.7,8,1 388 | 5.7,9.6,4.6,9,2 389 | 2.1,9.2,9.9,4,2 390 | 8.4,5.8,6.4,8,2 391 | 7.8,7.8,6.2,7,1 392 | 4.2,1.4,9.3,3,1 393 | 1.8,7.8,1.0,3,1 394 | 4.9,9.3,7.9,9,2 395 | 8.9,0.4,0.5,9,0 396 | 8.6,2.9,5.3,0,1 397 | 7.2,2.9,3.8,6,1 398 | 7.8,5.4,4.2,4,1 399 | 6.4,6.8,4.4,5,1 400 | 8.4,7.3,5.0,0,1 401 | 5.0,3.5,7.2,7,1 402 | 0.2,3.5,9.7,2,1 403 | 3.4,8.3,9.3,2,2 404 | 7.4,1.8,2.5,1,1 405 | 4.9,3.7,2.0,8,1 406 | 5.2,2.3,1.8,3,0 407 | 3.2,3.7,3.0,8,1 408 | 5.0,7.3,6.8,0,1 409 | 1.5,6.3,6.4,3,1 410 | 9.1,2.0,5.4,7,1 411 | 0.6,2.4,0.0,8,0 412 | 4.8,8.0,2.8,8,1 413 | 0.1,7.3,2.1,0,0 414 | 7.6,3.2,4.0,1,1 415 | 8.3,3.0,8.7,5,1 416 | 3.3,8.2,2.2,3,1 417 | 3.4,4.9,8.8,6,1 418 | 3.7,3.3,1.2,5,0 419 | 7.9,6.5,0.6,3,1 420 | 3.2,8.5,4.4,7,1 421 | 6.7,6.6,2.8,9,1 422 | 8.4,4.3,9.4,4,2 423 | 5.2,3.1,2.5,9,1 424 | 6.3,5.2,5.9,8,1 425 | 1.7,2.2,5.4,7,1 426 | 5.0,2.1,7.1,3,1 427 | 2.3,1.2,7.7,6,1 428 | 0.7,5.2,3.0,8,1 429 | 9.6,8.4,8.1,9,2 430 | 3.9,5.6,6.0,6,1 431 | 2.5,6.5,9.5,8,1 432 | 9.9,2.7,1.8,6,1 433 | 0.1,3.4,0.2,1,0 434 | 8.5,1.1,9.3,5,1 435 | 5.9,8.2,1.1,3,1 436 | 0.3,8.3,0.0,5,0 437 | 5.1,9.1,3.2,6,1 438 | 8.4,9.9,8.1,5,2 439 | 1.1,6.9,0.9,0,0 440 | 3.8,4.2,6.3,7,1 441 | 4.0,6.3,6.3,2,1 442 | 4.0,0.5,9.2,8,1 443 | 8.2,7.4,4.0,9,1 444 | 6.8,4.9,8.2,1,1 445 | 8.6,4.6,1.3,5,1 446 | 8.0,8.1,9.4,0,2 447 | 4.9,5.1,1.4,5,1 448 | 0.4,0.7,5.4,4,0 449 | 9.8,4.1,3.5,2,1 450 | 5.6,1.3,1.1,4,0 451 | 6.6,2.1,8.1,2,1 452 | 6.1,9.1,8.5,3,2 453 | 6.0,9.9,9.2,2,2 454 | 9.1,7.4,0.8,1,1 455 | 2.7,2.5,8.6,4,1 456 | 1.4,0.3,9.2,4,1 457 | 7.2,4.3,9.9,8,2 458 | 5.5,1.7,6.0,4,1 459 | 3.2,9.8,8.3,4,2 460 | 5.0,8.0,9.1,5,2 461 | 2.4,5.6,4.0,4,1 462 | 3.8,1.9,4.4,4,1 463 | 6.3,2.1,6.5,4,1 464 | 9.2,1.8,6.8,0,1 465 | 7.8,5.9,5.6,3,1 466 | 3.9,6.1,3.2,3,1 467 | 5.3,6.2,7.5,5,1 468 | 0.8,3.4,3.3,9,0 469 | 6.7,0.1,9.8,8,1 470 | 8.9,2.7,2.9,9,1 471 | 7.7,1.0,6.1,0,1 472 | 7.9,7.4,7.6,1,2 473 | 0.0,3.0,0.3,1,0 474 | 6.4,3.8,6.7,5,1 475 | 2.6,9.5,1.0,5,1 476 | 3.3,3.6,0.9,4,0 477 | 0.0,5.5,6.9,4,1 478 | 0.3,3.9,7.7,5,1 479 | 1.6,2.5,4.6,7,1 480 | 4.3,0.0,5.6,7,1 481 | 5.5,5.2,7.8,1,1 482 | 5.3,6.0,0.0,2,0 483 | 9.9,9.5,3.9,7,2 484 | 0.7,1.6,6.6,5,1 485 | 5.0,5.8,7.9,8,1 486 | 7.7,1.2,1.1,1,0 487 | 3.4,0.6,6.9,5,1 488 | 4.4,7.0,7.2,2,1 489 | 0.7,2.9,6.0,2,1 490 | 7.3,4.6,8.6,4,1 491 | 9.7,4.7,8.8,9,2 492 | 7.6,8.7,4.5,4,1 493 | 8.7,3.9,3.7,6,1 494 | 4.3,5.8,0.1,3,0 495 | 2.9,4.8,5.5,8,1 496 | 3.4,2.9,0.0,3,0 497 | 0.4,7.1,0.1,8,0 498 | 2.6,4.0,8.9,0,1 499 | 7.0,9.6,0.7,4,1 500 | 3.8,8.1,2.9,2,1 501 | 0.3,7.4,5.0,0,1 502 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /test/support/model.json: -------------------------------------------------------------------------------- 1 | {"learner":{"attributes":{},"feature_names":["x0","x1","x2","x3"],"feature_types":["float","float","float","int"],"gradient_booster":{"model":{"cats":{"enc":[],"feature_segments":[],"sorted_idx":[]},"gbtree_model_param":{"num_parallel_tree":"1","num_trees":"10"},"iteration_indptr":[0,1,2,3,4,5,6,7,8,9,10],"tree_info":[0,0,0,0,0,0,0,0,0,0],"trees":[{"base_weights":[-6.3367063E-9,-3.609117E-1,2.2825225E-1,-5.9661376E-1,-8.436364E-2,8.8812776E-2,7.3149997E-1,-8.043243E-1,-2.8987655E-1,-2.3174603E-1,1.9428554E-3,1.690721E-2,2.2653332E-1,2.9190475E-1,9.323457E-1,-2.8653848E-1,-4.1055557E-1,-6.94E-1,-4.9259264E-2,-5.657143E-1,-6.0444452E-2,6.594976E-3,2.0533332E-1,1.9090892E-3,3.8574713E-1,9.6969694E-2,2.265E-1,3.3777776E-1,2.8992E-1,-2.384E-1,-1.1916667E-1,-7.7185184E-1,9.999991E-4,-1.9466667E-1,1.8571411E-3,-6.611111E-1,9.999991E-4,-1.49E-1,1.8571411E-3,-9.4000004E-2,1.8492058E-2,1.4999986E-3,1.5100001E-1,1.8818182E-1,2.6425E-1,3.3777776E-1,1.7777762E-3,1.5100001E-1,9.999991E-4,-1.49E-1,1.7142841E-3,-2.6075003E-1,9.999991E-4,-1.49E-1,1.4999986E-3,-2.384E-1,9.999991E-4,1.7499984E-3,-9.8666675E-2,1.972601E-3,2.6833331E-2,1.952941E-2,1.5166667E-1,9.999991E-4,1.5100001E-1],"categories":[],"categories_nodes":[],"categories_segments":[],"categories_sizes":[],"default_left":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":0,"left_children":[1,3,5,7,9,11,13,15,17,19,-1,21,23,25,27,-1,29,31,33,35,37,39,41,-1,43,45,-1,47,-1,-1,49,51,-1,53,-1,55,-1,-1,-1,57,59,-1,-1,61,-1,63,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"loss_changes":[2.487843E1,7.5760717E0,1.2916965E1,3.780735E0,7.378514E-1,1.4419897E0,3.2594757E0,1.8049965E0,2.5912693E0,1.1672008E0,0E0,1.8712598E-1,1.750248E0,1.1906167E0,2.202549E-1,0E0,1.2484107E0,5.454602E-1,1.4633551E-1,3.8220096E-1,4.390891E-1,1.1303942E-1,2.9598004E-1,0E0,2.6707869E0,2.3916319E-1,0E0,1.6442966E-1,0E0,0E0,3.799786E-1,6.818304E-1,0E0,3.0398E-1,0E0,5.3509045E-1,0E0,0E0,0E0,2.3641592E-1,7.0435524E-2,0E0,0E0,8.264876E-1,0E0,1.6442966E-1,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0],"parents":[2147483647,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,11,11,12,12,13,13,14,14,16,16,17,17,18,18,19,19,20,20,21,21,22,22,24,24,25,25,27,27,30,30,31,31,33,33,35,35,39,39,40,40,43,43,45,45],"right_children":[2,4,6,8,10,12,14,16,18,20,-1,22,24,26,28,-1,30,32,34,36,38,40,42,-1,44,46,-1,48,-1,-1,50,52,-1,54,-1,56,-1,-1,-1,58,60,-1,-1,62,-1,64,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"split_conditions":[4.1E0,4.5E0,7.8E0,5.9E0,3.4E0,6.8E0,6E0,6E0,1.9E0,6.5E0,1.9428554E-3,9.7E0,6.3E0,8.4E0,1.5E0,-2.8653848E-1,2.7E0,3.9E0,8E-1,2.3E0,1E-1,7E-1,3.1E0,1.9090892E-3,6E0,8.3E0,2.265E-1,6E-1,2.8992E-1,-2.384E-1,1.4E0,9E0,9.999991E-4,8.2E0,1.8571411E-3,6E0,9.999991E-4,-1.49E-1,1.8571411E-3,6E-1,9E0,1.4999986E-3,1.5100001E-1,8.6E0,2.6425E-1,6.7E0,1.7777762E-3,1.5100001E-1,9.999991E-4,-1.49E-1,1.7142841E-3,-2.6075003E-1,9.999991E-4,-1.49E-1,1.4999986E-3,-2.384E-1,9.999991E-4,1.7499984E-3,-9.8666675E-2,1.972601E-3,2.6833331E-2,1.952941E-2,1.5166667E-1,9.999991E-4,1.5100001E-1],"split_indices":[2,1,1,0,0,0,2,3,2,1,0,2,2,0,0,0,1,1,1,2,2,0,1,0,1,1,0,0,0,0,0,3,0,0,0,3,0,0,0,0,3,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"split_type":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sum_hessian":[3E2,1.16E2,1.84E2,6.2E1,5.4E1,1.45E2,3.9E1,3.6E1,2.6E1,2E1,3.4E1,9.6E1,4.9E1,1.3E1,2.6E1,2.5E1,1.1E1,9E0,1.7E1,6E0,1.4E1,9.2E1,4E0,2.1E1,2.8E1,1E1,3E0,2E0,2.4E1,4E0,7E0,8E0,1E0,4E0,1.3E1,5E0,1E0,1E0,1.3E1,9E0,8.3E1,3E0,1E0,2.1E1,7E0,2E0,8E0,1E0,1E0,1E0,6E0,7E0,1E0,1E0,3E0,4E0,1E0,7E0,2E0,7.2E1,1.1E1,1.6E1,5E0,1E0,1E0],"tree_param":{"num_deleted":"0","num_feature":"4","num_nodes":"65","size_leaf_vector":"1"}},{"base_weights":[-3.7965982E-4,-2.6510158E-1,1.6704112E-1,-4.262541E-1,-5.5463757E-2,6.574253E-2,5.3260493E-1,-5.957292E-1,-2.275703E-1,-1.5176383E-1,1.3741927E-3,1.4680334E-2,1.877129E-1,2.3149602E-1,2.0070283E-1,-2.0413458E-1,-3.4902772E-1,-5.019704E-1,-3.7831686E-2,-1.5992668E-1,-7.977833E-2,-4.9069314E-4,2.6255557E-1,6.510735E-2,4.656405E-1,8.185858E-2,1.755375E-1,-1.8654802E-1,-1.04310215E-1,-5.941824E-1,1.0390461E-3,-1.6576669E-1,1.3537434E-3,-2.9970667E-1,1.339789E-3,-9.555556E-2,8.916719E-3,-4.661111E-2,4.2879167E-1,3.8755376E-2,1.2835E-1,1.0704562E-3,6.039054E-1,2.871111E-1,1.3037047E-3,-1.1324001E-1,1.2380928E-3,-1.962502E-1,8.50001E-4,-1.2665E-1,1.1624963E-3,1.1333347E-3,-1.5098669E-1,1.2999997E-3,-1.342E-1,-6.640648E-4,2.2760581E-2,7.749975E-4,-2.1749998E-2,7.749975E-4,1.71E-1,2.5200422E-3,5.6655463E-2,1.9910625E-1,7.2955385E-2,8.50001E-4,1.2835E-1],"categories":[],"categories_nodes":[],"categories_segments":[],"categories_sizes":[],"default_left":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":1,"left_children":[1,3,5,7,9,11,13,15,17,19,-1,21,23,25,-1,-1,27,29,31,-1,33,35,37,39,41,43,-1,-1,45,47,-1,49,-1,51,-1,53,55,57,59,61,-1,-1,63,65,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"loss_changes":[1.3384587E1,3.9290466E0,6.815739E0,2.0867777E0,3.0137375E-1,9.0654314E-1,1.4880304E0,4.940195E-1,1.6470674E0,5.0646853E-1,0E0,3.9122307E-1,1.4349283E0,6.929283E-1,0E0,0E0,6.694826E-1,6.0794926E-1,1.0909404E-1,0E0,3.348372E-1,8.938916E-2,3.2835445E-1,2.7973408E-1,8.283942E-1,1.7375922E-1,0E0,0E0,2.0890002E-1,3.9578462E-1,0E0,2.1911648E-1,0E0,3.1082147E-1,0E0,3.1818667E-1,6.7980446E-2,4.0080575E-3,2.3926419E-1,1.3509974E-1,0E0,0E0,5.4247856E-2,1.188004E-1,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0],"parents":[2147483647,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,11,11,12,12,13,13,16,16,17,17,18,18,20,20,21,21,22,22,23,23,24,24,25,25,28,28,29,29,31,31,33,33,35,35,36,36,37,37,38,38,39,39,42,42,43,43],"right_children":[2,4,6,8,10,12,14,16,18,20,-1,22,24,26,-1,-1,28,30,32,-1,34,36,38,40,42,44,-1,-1,46,48,-1,50,-1,52,-1,54,56,58,60,62,-1,-1,64,66,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"split_conditions":[4.1E0,5.1E0,7.8E0,3.7E0,3.4E0,5.8E0,6E0,6E0,1.9E0,8E-1,1.3741927E-3,9.8E0,7E0,8.4E0,2.0070283E-1,-2.0413458E-1,2.6E0,3.9E0,8E-1,-1.5992668E-1,6.5E0,7E-1,3E-1,9.7E0,6.3E0,8.3E0,1.755375E-1,-1.8654802E-1,1.3E0,9E0,1.0390461E-3,8.2E0,1.3537434E-3,5.6E0,1.339789E-3,6E-1,5E0,2.9E0,5.9E0,9E0,1.2835E-1,1.0704562E-3,9.4E0,6.7E0,1.3037047E-3,-1.1324001E-1,1.2380928E-3,-1.962502E-1,8.50001E-4,-1.2665E-1,1.1624963E-3,1.1333347E-3,-1.5098669E-1,1.2999997E-3,-1.342E-1,-6.640648E-4,2.2760581E-2,7.749975E-4,-2.1749998E-2,7.749975E-4,1.71E-1,2.5200422E-3,5.6655463E-2,1.9910625E-1,7.2955385E-2,8.50001E-4,1.2835E-1],"split_indices":[2,1,1,0,0,1,2,3,2,2,0,2,0,0,0,0,2,1,1,0,1,0,1,2,2,1,0,0,0,3,0,0,0,1,0,0,1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"split_type":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sum_hessian":[3E2,1.16E2,1.84E2,6.5E1,5.1E1,1.45E2,3.9E1,3.4E1,3.1E1,1.9E1,3.2E1,1.03E2,4.2E1,1.3E1,2.6E1,2.4E1,1E1,1.2E1,1.9E1,2E0,1.7E1,9.8E1,5E0,3E1,1.2E1,1E1,3E0,4E0,6E0,1E1,2E0,4E0,1.5E1,4E0,1.3E1,8E0,9E1,2E0,3E0,2.9E1,1E0,3E0,9E0,2E0,8E0,1E0,5E0,9E0,1E0,1E0,3E0,2E0,2E0,7E0,1E0,7.8E1,1.2E1,1E0,1E0,1E0,2E0,2.5E1,4E0,7E0,2E0,1E0,1E0],"tree_param":{"num_deleted":"0","num_feature":"4","num_nodes":"67","size_leaf_vector":"1"}},{"base_weights":[-6.672397E-4,-1.9521423E-1,1.2237419E-1,-3.1081995E-1,-4.4729743E-2,4.9448915E-2,3.854921E-1,-4.3137282E-1,-1.6925216E-1,-1.2165669E-1,9.7442983E-4,3.3082347E-2,2.9490864E-1,1.8390729E-1,4.7574005E-1,-4.8011968E-1,-2.372019E-1,-3.6633015E-1,-3.2688864E-2,-1.2794134E-1,-6.409559E-2,8.155842E-3,1.5114342E-1,-2.8747916E-2,1.3845849E-1,6.916496E-2,1.3604157E-1,1.5330923E-1,1.4862065E-1,-1.4878273E-1,-1.882237E-1,-1.23526044E-1,-8.1300534E-2,-4.336913E-1,8.3124044E-4,-1.4113416E-1,9.7300374E-4,-2.3976533E-1,9.6656085E-4,-9.594134E-3,6.928438E-2,-1.4988652E-3,3.3167508E-1,8.258343E-4,-1.8487502E-2,2.4404442E-1,9.560426E-4,9.8244585E-2,-2.9255427E-2,-8.5257806E-2,5.571395E-4,-7.539904E-2,8.9142326E-4,-1.4326264E-1,7.224977E-4,-1.076525E-1,9.009406E-4,9.066702E-4,-1.2078934E-1,-2.1198442E-2,-8.323859E-4,-2.6666988E-3,5.8575206E-2,7.133947E-4,-7.784683E-3,4.891545E-4,1.365714E-1,7.224977E-4,1.09097496E-1],"categories":[],"categories_nodes":[],"categories_segments":[],"categories_sizes":[],"default_left":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":2,"left_children":[1,3,5,7,9,11,13,15,17,19,-1,21,23,25,27,29,31,33,35,-1,37,39,41,43,-1,45,-1,47,-1,-1,49,-1,51,53,-1,55,-1,57,-1,59,61,63,65,-1,-1,67,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"loss_changes":[7.229028E0,2.021531E0,3.5307074E0,1.0533714E0,1.9231617E-1,5.767745E-1,6.40213E-1,2.1739721E-1,8.492609E-1,3.2357422E-1,0E0,4.0488058E-1,4.986158E-1,4.0166223E-1,9.522152E-2,3.1354427E-2,2.1143967E-1,3.2442153E-1,7.839133E-2,0E0,2.1363401E-1,1.2544319E-1,6.618596E-1,4.312245E-3,0E0,1.2614274E-1,0E0,1.6299722E-1,0E0,0E0,5.525338E-2,0E0,8.6719155E-2,2.1150684E-1,0E0,1.579765E-1,0E0,1.989258E-1,0E0,3.72844E-2,2.5776306E-1,1.3887552E-3,4.4785166E-1,0E0,0E0,8.583327E-2,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0],"parents":[2147483647,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,20,20,21,21,22,22,23,23,25,25,27,27,30,30,32,32,33,33,35,35,37,37,39,39,40,40,41,41,42,42,45,45],"right_children":[2,4,6,8,10,12,14,16,18,20,-1,22,24,26,28,30,32,34,36,-1,38,40,42,44,-1,46,-1,48,-1,-1,50,-1,52,54,-1,56,-1,58,-1,60,62,64,66,-1,-1,68,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"split_conditions":[4.1E0,5.1E0,7.8E0,3.7E0,3.4E0,9.7E0,6E0,3.2E0,1.9E0,8E-1,9.7442983E-4,6.9E0,2.3E0,8.4E0,1.5E0,8E0,2.2E0,3.9E0,8E-1,-1.2794134E-1,6.5E0,7.6E0,6.4E0,2.9E0,1.3845849E-1,8.3E0,1.3604157E-1,6E-1,1.4862065E-1,-1.4878273E-1,8E-1,-1.23526044E-1,1E-1,9E0,8.3124044E-4,8.2E0,9.7300374E-4,5.6E0,9.6656085E-4,7E-1,4.9E0,9E0,4.2E0,8.258343E-4,-1.8487502E-2,6.7E0,9.560426E-4,9.8244585E-2,-2.9255427E-2,-8.5257806E-2,5.571395E-4,-7.539904E-2,8.9142326E-4,-1.4326264E-1,7.224977E-4,-1.076525E-1,9.009406E-4,9.066702E-4,-1.2078934E-1,-2.1198442E-2,-8.323859E-4,-2.6666988E-3,5.8575206E-2,7.133947E-4,-7.784683E-3,4.891545E-4,1.365714E-1,7.224977E-4,1.09097496E-1],"split_indices":[2,1,1,0,0,2,2,2,2,2,0,1,1,0,0,3,1,1,1,0,1,0,2,0,0,1,0,0,0,0,0,0,0,3,0,0,0,1,0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"split_type":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sum_hessian":[3E2,1.16E2,1.84E2,6.5E1,5.1E1,1.45E2,3.9E1,3.4E1,3.1E1,1.9E1,3.2E1,1.37E2,8E0,1.3E1,2.6E1,2.6E1,8E0,1.2E1,1.9E1,2E0,1.7E1,1.14E2,2.3E1,3E0,5E0,1E1,3E0,2E0,2.4E1,2.4E1,2E0,3E0,5E0,1E1,2E0,4E0,1.5E1,4E0,1.3E1,8.9E1,2.5E1,1.3E1,1E1,2E0,1E0,2E0,8E0,1E0,1E0,1E0,1E0,1E0,4E0,9E0,1E0,1E0,3E0,2E0,2E0,8E0,8.1E1,1.6E1,9E0,1.2E1,1E0,3E0,7E0,1E0,1E0],"tree_param":{"num_deleted":"0","num_feature":"4","num_nodes":"69","size_leaf_vector":"1"}},{"base_weights":[-1.0510988E-3,-1.4455497E-1,8.9711085E-2,-2.403936E-1,-4.9501713E-2,3.6564E-2,2.8145516E-1,-3.2206964E-1,-1.2748194E-1,-1.22153684E-1,6.978273E-4,5.8307135E-3,1.1004534E-1,1.4636493E-1,3.410777E-1,-1.07228346E-1,-1.8806699E-1,-2.7436027E-1,-3.3583988E-2,-1.03305615E-1,-7.6201536E-2,-2.5194474E-3,1.4263658E-1,3.9376464E-2,2.7009842E-1,5.8486007E-2,1.0543222E-1,1.3031285E-1,1.05817914E-1,-9.57327E-2,-6.936119E-2,-3.0529964E-1,6.141186E-4,-1.2014421E-1,6.875369E-4,-1.898861E-1,6.973079E-4,-1.1392746E-2,6.9502644E-2,-4.3500323E-2,2.4658012E-1,2.1060407E-2,8.832872E-2,5.8210522E-2,1.1857562E-1,2.0743775E-1,7.010937E-4,8.3507895E-2,-2.486712E-2,-6.408919E-2,6.6564564E-4,-1.03181E-1,5.694598E-4,-9.1504626E-2,6.982327E-4,-8.592908E-2,7.204995E-4,-1.6875258E-2,-4.6192185E-4,-4.238169E-3,1.3587022E-1,5.348772E-4,-2.0110024E-2,-1.5714385E-2,1.0910831E-1,4.7651576E-4,3.5430852E-2,-8.128327E-3,7.271029E-2,6.141186E-4,9.273288E-2],"categories":[],"categories_nodes":[],"categories_segments":[],"categories_sizes":[],"default_left":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":3,"left_children":[1,3,5,7,9,11,13,15,17,19,-1,21,23,25,27,-1,29,31,33,-1,35,37,39,41,43,45,-1,47,-1,-1,49,51,-1,53,-1,55,-1,57,59,61,63,65,-1,67,-1,69,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"loss_changes":[3.9334104E0,1.0539434E0,1.874977E0,4.9382854E-1,2.2620767E-1,3.2907355E-1,2.7225494E-1,8.913636E-2,3.493668E-1,2.2902209E-1,0E0,1.1916388E-1,4.7572762E-1,2.3175138E-1,2.0323992E-2,0E0,1.17865175E-1,8.614367E-2,5.306741E-2,0E0,1.9683938E-1,6.4060114E-2,1.268127E-1,1.3861749E-1,3.2173318E-1,9.151368E-2,0E0,1.177655E-1,0E0,0E0,6.2434845E-2,1.0747576E-1,0E0,1.13917306E-1,0E0,1.6776627E-1,0E0,3.9250255E-2,3.590971E-1,3.3164793E-3,1.591013E-1,5.650077E-2,0E0,1.0082388E-1,0E0,6.2014565E-2,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0],"parents":[2147483647,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,11,11,12,12,13,13,14,14,16,16,17,17,18,18,20,20,21,21,22,22,23,23,24,24,25,25,27,27,30,30,31,31,33,33,35,35,37,37,38,38,39,39,40,40,41,41,43,43,45,45],"right_children":[2,4,6,8,10,12,14,16,18,20,-1,22,24,26,28,-1,30,32,34,-1,36,38,40,42,44,46,-1,48,-1,-1,50,52,-1,54,-1,56,-1,58,60,62,64,66,-1,68,-1,70,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"split_conditions":[4.1E0,4.3E0,7.8E0,5.9E0,3.4E0,5.8E0,6E0,3.2E0,1.9E0,8E-1,6.978273E-4,9.8E0,7E0,8.4E0,1.5E0,-1.07228346E-1,2.2E0,9E0,8E-1,-1.03305615E-1,6.5E0,9E0,5.9E0,9.7E0,6.5E0,8.3E0,1.0543222E-1,6E-1,1.05817914E-1,-9.57327E-2,1E-1,3.9E0,6.141186E-4,8.2E0,6.875369E-4,2.3E0,6.973079E-4,5.1E0,4.9E0,2.9E0,3E-1,9E0,8.832872E-2,5E0,1.1857562E-1,6.7E0,7.010937E-4,8.3507895E-2,-2.486712E-2,-6.408919E-2,6.6564564E-4,-1.03181E-1,5.694598E-4,-9.1504626E-2,6.982327E-4,-8.592908E-2,7.204995E-4,-1.6875258E-2,-4.6192185E-4,-4.238169E-3,1.3587022E-1,5.348772E-4,-2.0110024E-2,-1.5714385E-2,1.0910831E-1,4.7651576E-4,3.5430852E-2,-8.128327E-3,7.271029E-2,6.141186E-4,9.273288E-2],"split_indices":[2,1,1,0,0,1,2,2,2,2,0,2,0,0,0,0,1,3,1,0,1,3,0,2,2,1,0,0,0,0,0,1,0,0,0,2,0,2,1,0,1,3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"split_type":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sum_hessian":[3E2,1.16E2,1.84E2,5.7E1,5.9E1,1.45E2,3.9E1,3.2E1,2.5E1,2.4E1,3.5E1,1.03E2,4.2E1,1.3E1,2.6E1,2.4E1,8E0,9E0,1.6E1,3E0,2.1E1,9.8E1,5E0,3E1,1.2E1,1E1,3E0,2E0,2.4E1,3E0,5E0,8E0,1E0,4E0,1.2E1,8E0,1.3E1,8.8E1,1E1,2E0,3E0,2.9E1,1E0,5E0,7E0,2E0,8E0,1E0,1E0,1E0,4E0,7E0,1E0,1E0,3E0,5E0,3E0,1.5E1,7.3E1,9E0,1E0,1E0,1E0,1E0,2E0,2.5E1,4E0,4E0,1E0,1E0,1E0],"tree_param":{"num_deleted":"0","num_feature":"4","num_nodes":"71","size_leaf_vector":"1"}},{"base_weights":[-2.538435E-5,-1.06706604E-1,6.7443416E-2,-1.6731681E-1,-2.7726216E-2,2.898869E-2,2.061171E-1,-2.2908898E-1,-9.4524845E-2,-6.797395E-2,-1.9384384E-2,1.9152151E-2,1.7659464E-1,1.3365076E-1,7.779981E-2,-7.339196E-2,-1.3193694E-1,-1.9542085E-1,-2.4216209E-2,-6.556131E-2,5.0314417E-4,-6.510688E-4,6.601955E-2,-2.0628601E-2,2.7864435E-1,6.619198E-2,8.063385E-2,-6.974814E-2,4.8124642E-4,-2.3144123E-1,5.3825974E-4,-1.02262214E-1,5.060043E-4,9.625516E-3,-1.9247693E-1,-8.3023505E-3,6.0618374E-2,1.5939172E-2,1.8571886E-1,6.533742E-4,-1.3357223E-2,1.0300469E-1,9.949013E-2,1.3573999E-2,1.5818954E-1,-7.648001E-2,5.220026E-4,-7.7778935E-2,5.4113567E-4,9.2462E-3,4.4728917E-4,-8.686622E-2,3.3546687E-4,-2.0786894E-2,-6.5292476E-4,-3.093873E-3,1.15489684E-1,3.3905322E-4,2.6927436E-2,-8.554567E-3,7.808958E-2,-1.7093522E-2,6.186408E-2,2.0026008E-2,-5.626005E-3,3.8560628E-4,7.870917E-2],"categories":[],"categories_nodes":[],"categories_segments":[],"categories_sizes":[],"default_left":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":4,"left_children":[1,3,5,7,9,11,13,15,17,-1,19,21,23,25,-1,-1,27,29,31,33,-1,35,37,39,41,43,-1,-1,-1,45,-1,47,-1,49,51,53,55,57,59,-1,-1,-1,61,63,65,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"loss_changes":[2.1736903E0,5.554422E-1,9.805667E-1,2.751156E-1,8.1865676E-2,2.0859984E-1,1.1959362E-1,2.0600915E-2,2.2227114E-1,0E0,4.971034E-2,1.281243E-1,1.8688723E-1,1.554057E-1,0E0,0E0,9.4372265E-2,9.27642E-2,4.0604822E-2,1.545308E-1,0E0,4.6444673E-2,2.4281785E-1,2.2768597E-3,3.539276E-2,6.5622896E-2,0E0,0E0,0E0,6.0700715E-2,0E0,8.215992E-2,0E0,1.8505912E-3,1.1308578E-1,3.272637E-2,2.5704014E-1,3.2694086E-2,1.9915268E-1,0E0,0E0,0E0,6.1846316E-2,1.8443326E-2,8.138971E-2,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0],"parents":[2147483647,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,10,10,11,11,12,12,13,13,16,16,17,17,18,18,19,19,21,21,22,22,23,23,24,24,25,25,29,29,31,31,33,33,34,34,35,35,36,36,37,37,38,38,42,42,43,43,44,44],"right_children":[2,4,6,8,10,12,14,16,18,-1,20,22,24,26,-1,-1,28,30,32,34,-1,36,38,40,42,44,-1,-1,-1,46,-1,48,-1,50,52,54,56,58,60,-1,-1,-1,62,64,66,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"split_conditions":[4.1E0,5.1E0,7.8E0,3.7E0,1E-1,9.7E0,7.6E0,3.4E0,1.9E0,-6.797395E-2,6.5E0,5.8E0,2.3E0,8.4E0,7.779981E-2,-7.339196E-2,4E0,3.9E0,8E-1,5.8E0,5.0314417E-4,9E0,7E0,2.9E0,9.9E0,8E0,8.063385E-2,-6.974814E-2,4.8124642E-4,9E0,5.3825974E-4,8.2E0,5.060043E-4,3.4E0,3.4E0,7E-1,4.9E0,9E0,3E0,6.533742E-4,-1.3357223E-2,1.0300469E-1,5.9E0,3E0,5.3E0,-7.648001E-2,5.220026E-4,-7.7778935E-2,5.4113567E-4,9.2462E-3,4.4728917E-4,-8.686622E-2,3.3546687E-4,-2.0786894E-2,-6.5292476E-4,-3.093873E-3,1.15489684E-1,3.3905322E-4,2.6927436E-2,-8.554567E-3,7.808958E-2,-1.7093522E-2,6.186408E-2,2.0026008E-2,-5.626005E-3,3.8560628E-4,7.870917E-2],"split_indices":[2,1,1,0,2,2,2,2,2,0,1,1,1,0,0,0,3,1,1,1,0,3,0,0,2,3,0,0,0,3,0,0,0,0,0,0,1,3,3,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"split_type":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sum_hessian":[3E2,1.16E2,1.84E2,6.5E1,5.1E1,1.45E2,3.9E1,3.4E1,3.1E1,1E0,5E1,1.37E2,8E0,1.8E1,2.1E1,2.8E1,6E0,1.2E1,1.9E1,1.5E1,3.5E1,9.7E1,4E1,3E0,5E0,1.3E1,5E0,3E0,3E0,1E1,2E0,4E0,1.5E1,1E1,5E0,8.7E1,1E1,2.9E1,1.1E1,2E0,1E0,3E0,2E0,9E0,4E0,9E0,1E0,1E0,3E0,2E0,8E0,3E0,2E0,7E0,8E1,9E0,1E0,2.5E1,4E0,3E0,8E0,1E0,1E0,3E0,6E0,2E0,2E0],"tree_param":{"num_deleted":"0","num_feature":"4","num_nodes":"67","size_leaf_vector":"1"}},{"base_weights":[4.3285827E-4,-7.844776E-2,5.0317183E-2,-1.2917162E-1,-2.8107226E-2,2.2503229E-2,1.5058018E-1,-1.7394523E-1,-7.839991E-2,-5.6236573E-2,-1.9380482E-2,1.4767057E-2,1.3862418E-1,-3.4644008E-3,1.5941714E-1,-6.418928E-2,-1.4660546E-1,-1.3476218E-1,4.0060788E-4,-5.138018E-2,3.1158975E-3,5.7169558E-3,7.8846194E-2,2.7683556E-2,8.1113726E-2,1.1684281E-1,5.5520777E-2,-5.1193293E-2,-5.806765E-2,-1.8973292E-1,-2.9127866E-2,-1.13571696E-1,3.4637793E-4,2.5702516E-2,9.681474E-4,-6.055295E-3,3.5152417E-2,-2.290529E-2,1.9617978E-1,-2.2909075E-2,8.591255E-2,3.4992788E-2,1.857103E-1,-4.2730685E-2,5.91757E-3,-2.7439361E-2,-6.4545155E-2,-3.201142E-2,4.7571957E-4,-6.347854E-2,-8.199134E-3,1.1374292E-2,1.9184053E-4,5.3675474E-5,3.5212524E-4,-3.290011E-4,-1.6651338E-2,2.4125577E-4,3.6282774E-2,-3.0234973E-3,-2.0752199E-2,-1.7204933E-2,1.0793024E-1,5.22697E-4,-1.4529491E-2,5.0014276E-2,-1.1353631E-2,-7.4789217E-3,2.6375027E-2,6.5094784E-2,2.771228E-2],"categories":[],"categories_nodes":[],"categories_segments":[],"categories_sizes":[],"default_left":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":5,"left_children":[1,3,5,7,9,11,13,15,17,-1,19,21,23,-1,25,-1,27,29,-1,31,33,35,37,39,-1,41,-1,-1,43,45,47,49,-1,51,53,55,57,59,61,63,65,67,69,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"loss_changes":[1.1883541E0,2.9512495E-1,5.125228E-1,1.18210256E-1,7.980241E-2,1.2910935E-1,5.9149742E-2,1.455009E-3,1.3050772E-1,0E0,4.191298E-2,7.957856E-2,1.24067664E-1,0E0,1.9876659E-2,0E0,3.4786314E-2,9.318903E-2,0E0,7.8544326E-2,1.6729806E-3,4.2488493E-2,2.074543E-1,1.9643925E-2,0E0,8.932719E-2,0E0,0E0,4.2188935E-2,7.7965558E-3,1.6847856E-2,8.720729E-2,0E0,8.939493E-4,5.144848E-6,2.1516716E-2,1.03038624E-1,5.2377135E-3,3.524271E-1,2.6010505E-3,3.6308944E-2,3.0733783E-2,6.225854E-3,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0],"parents":[2147483647,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,10,10,11,11,12,12,14,14,16,16,17,17,19,19,20,20,21,21,22,22,23,23,25,25,28,28,29,29,30,30,31,31,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,41,41,42,42],"right_children":[2,4,6,8,10,12,14,16,18,-1,20,22,24,-1,26,-1,28,30,-1,32,34,36,38,40,-1,42,-1,-1,44,46,48,50,-1,52,54,56,58,60,62,64,66,68,70,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"split_conditions":[4.1E0,4.3E0,7.8E0,3.7E0,2E-1,9.7E0,1E0,1.4E0,2.5E0,-5.6236573E-2,2.3E0,9E0,3.1E0,-3.4644008E-3,7.6E0,-6.418928E-2,3.2E0,6E0,4.0060788E-4,6.5E0,4.4E0,5.8E0,5E0,5.9E0,8.1113726E-2,5.4E0,5.5520777E-2,-5.1193293E-2,2.2E0,6.8E0,4E-1,3.1E0,3.4637793E-4,3.4E0,5.6E0,8.9E0,6.9E0,4.9E0,5.7E0,2.9E0,6.9E0,8.9E0,5.5E0,-4.2730685E-2,5.91757E-3,-2.7439361E-2,-6.4545155E-2,-3.201142E-2,4.7571957E-4,-6.347854E-2,-8.199134E-3,1.1374292E-2,1.9184053E-4,5.3675474E-5,3.5212524E-4,-3.290011E-4,-1.6651338E-2,2.4125577E-4,3.6282774E-2,-3.0234973E-3,-2.0752199E-2,-1.7204933E-2,1.0793024E-1,5.22697E-4,-1.4529491E-2,5.0014276E-2,-1.1353631E-2,-7.4789217E-3,2.6375027E-2,6.5094784E-2,2.771228E-2],"split_indices":[2,1,1,0,2,2,3,0,2,0,2,3,1,0,2,0,2,3,0,1,1,1,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,1,2,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"split_type":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sum_hessian":[3E2,1.16E2,1.84E2,5.7E1,5.9E1,1.45E2,3.9E1,2.9E1,2.8E1,2E0,5.7E1,1.37E2,8E0,2E0,3.7E1,9E0,2E1,1.6E1,1.2E1,2.3E1,3.4E1,1.21E2,1.6E1,5E0,3E0,1.6E1,2.1E1,1.5E1,5E0,1E1,6E0,1E1,1.3E1,2E0,3.2E1,8.7E1,3.4E1,9E0,7E0,3E0,2E0,8E0,8E0,2E0,3E0,3E0,7E0,1E0,5E0,4E0,6E0,1E0,1E0,7E0,2.5E1,8E1,7E0,2.5E1,9E0,8E0,1E0,3E0,4E0,2E0,1E0,1E0,1E0,4E0,4E0,5E0,3E0],"tree_param":{"num_deleted":"0","num_feature":"4","num_nodes":"71","size_leaf_vector":"1"}},{"base_weights":[1.8708645E-4,-5.8254555E-2,3.7146464E-2,-9.516695E-2,-2.1601662E-2,1.7069122E-2,1.095001E-1,-1.2473518E-1,-5.3977784E-2,-4.4989258E-2,-1.4589779E-2,1.0963708E-2,1.08788885E-1,4.976588E-2,1.2775265E-1,-4.1927934E-2,-8.679548E-2,-9.43281E-2,2.874762E-4,-3.893825E-2,2.5231652E-3,-8.189844E-3,2.9153537E-2,-1.50389075E-2,1.7320926E-1,-1.3106912E-2,4.8921E-2,1.0157585E-1,4.9119886E-2,-2.5989477E-2,-4.2980976E-2,-5.0209653E-2,-4.107284E-2,-8.601633E-2,2.4989122E-4,2.1847129E-2,6.8998156E-4,-2.182947E-1,1.4844654E-3,1.5480616E-2,8.8964984E-2,4.1816238E-4,-9.650588E-3,6.391051E-2,6.237185E-2,-2.0482996E-2,1.8042685E-3,-1.9171305E-2,1.1364873E-1,-2.441279E-2,3.7320936E-3,-2.5071857E-2,3.5679343E-4,-6.889433E-3,-5.7184603E-2,9.668148E-3,1.6306044E-4,2.9238066E-5,2.62707E-4,6.958947E-3,-1.05191566E-1,2.7349421E-3,-1.6496148E-2,1.2793043E-3,1.9211002E-2,1.0022972E-2,6.1683435E-2,-1.2350071E-2,4.0417403E-2,-2.7715208E-3,-7.1387086E-3,-5.1252247E-4,3.8196195E-2],"categories":[],"categories_nodes":[],"categories_segments":[],"categories_sizes":[],"default_left":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":6,"left_children":[1,3,5,7,9,11,13,15,17,-1,19,21,23,25,27,-1,29,31,-1,33,35,37,39,41,43,45,-1,47,-1,49,-1,-1,51,53,-1,55,57,59,61,63,65,-1,-1,-1,67,69,-1,-1,71,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"loss_changes":[6.5231395E-1,1.5623894E-1,2.6687464E-1,6.39047E-2,5.1815838E-2,8.0565386E-2,3.7254333E-2,6.680429E-3,5.772423E-2,0E0,2.4265341E-2,4.8317946E-2,7.439819E-2,8.049855E-2,1.1624128E-2,0E0,3.7484303E-2,5.1470205E-2,0E0,4.5008115E-2,1.224779E-3,1.3854216E-1,5.6686368E-2,1.170795E-3,1.3197705E-2,1.2515027E-3,0E0,4.7219038E-2,0E0,1.591178E-2,0E0,0E0,1.8060818E-2,6.816898E-2,0E0,6.458793E-4,3.5458506E-6,1.0401319E-1,2.8946752E-2,3.185937E-2,7.737447E-2,0E0,0E0,0E0,2.8020166E-2,3.6654528E-6,0E0,0E0,3.018345E-2,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0],"parents":[2147483647,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,10,10,11,11,12,12,13,13,14,14,16,16,17,17,19,19,20,20,21,21,22,22,23,23,24,24,25,25,27,27,29,29,32,32,33,33,35,35,36,36,37,37,38,38,39,39,40,40,44,44,45,45,48,48],"right_children":[2,4,6,8,10,12,14,16,18,-1,20,22,24,26,28,-1,30,32,-1,34,36,38,40,42,44,46,-1,48,-1,50,-1,-1,52,54,-1,56,58,60,62,64,66,-1,-1,-1,68,70,-1,-1,72,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"split_conditions":[4.1E0,4.3E0,7.8E0,5.9E0,2E-1,9.7E0,5.3E0,2.7E0,2.5E0,-4.4989258E-2,2.3E0,5E0,2.3E0,8.4E0,8E0,-4.1927934E-2,3.3E0,1.3E0,2.874762E-4,6.5E0,4.4E0,7E-1,7.6E0,2.9E0,9.9E0,8E0,4.8921E-2,1.5E0,4.9119886E-2,2E0,-4.2980976E-2,-5.0209653E-2,6E0,5.5E0,2.4989122E-4,3.4E0,3E0,6E-1,8.9E0,6.8E0,7.9E0,4.1816238E-4,-9.650588E-3,6.391051E-2,5.9E0,9.3E0,1.8042685E-3,-1.9171305E-2,6E0,-2.441279E-2,3.7320936E-3,-2.5071857E-2,3.5679343E-4,-6.889433E-3,-5.7184603E-2,9.668148E-3,1.6306044E-4,2.9238066E-5,2.62707E-4,6.958947E-3,-1.05191566E-1,2.7349421E-3,-1.6496148E-2,1.2793043E-3,1.9211002E-2,1.0022972E-2,6.1683435E-2,-1.2350071E-2,4.0417403E-2,-2.7715208E-3,-7.1387086E-3,-5.1252247E-4,3.8196195E-2],"split_indices":[2,1,1,0,2,2,2,1,2,0,2,3,1,0,3,0,1,1,0,1,1,0,0,0,2,3,0,0,0,2,0,0,3,1,0,0,0,0,0,1,2,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"split_type":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sum_hessian":[3E2,1.16E2,1.84E2,5.7E1,5.9E1,1.45E2,3.9E1,3.2E1,2.5E1,2E0,5.7E1,1.37E2,8E0,1E1,2.9E1,2.1E1,1.1E1,1.4E1,1.1E1,2.3E1,3.4E1,6.7E1,7E1,3E0,5E0,7E0,3E0,1.9E1,1E1,6E0,5E0,5E0,9E0,1E1,1.3E1,2E0,3.2E1,2E0,6.5E1,5.8E1,1.2E1,2E0,1E0,3E0,2E0,5E0,2E0,1E0,1.8E1,2E0,4E0,4E0,5E0,7E0,3E0,1E0,1E0,8E0,2.4E1,1E0,1E0,5.8E1,7E0,4.8E1,1E1,9E0,3E0,1E0,1E0,2E0,3E0,2E0,1.6E1],"tree_param":{"num_deleted":"0","num_feature":"4","num_nodes":"73","size_leaf_vector":"1"}},{"base_weights":[-2.2624935E-4,-3.4486257E-2,3.4035258E-2,-8.306986E-2,-1.5622642E-2,8.718589E-3,6.650796E-2,-7.055729E-2,-8.941282E-2,-5.3174444E-2,4.1388795E-3,4.1166414E-3,4.763341E-2,6.9795004E-3,8.541747E-2,-9.581836E-2,-3.6264546E-2,-8.198446E-2,-1.6608618E-2,-2.7938051E-2,6.7832205E-3,1.1887244E-3,7.691833E-2,-7.015884E-3,7.272416E-2,-1.7097136E-2,5.396952E-2,4.0382657E-2,1.073935E-1,-9.973765E-2,-1.4390945E-3,-7.741801E-2,2.1097281E-4,-1.0102694E-1,2.4202809E-2,-7.418411E-2,5.14039E-3,2.6240277E-3,5.2097004E-2,5.6424886E-3,-5.1712275E-2,6.352598E-3,2.826065E-2,-2.4670195E-3,2.1550656E-4,2.5734543E-3,-2.3090018E-2,4.9884368E-2,-1.7490626E-3,-2.8568614E-2,6.328587E-2,8.847366E-2,8.578223E-2,-2.4386514E-2,-4.3862265E-2,-3.7657242E-2,4.2287037E-3,-3.845144E-2,-1.2018318E-2,1.4129878E-4,1.4309738E-2,6.851256E-5,-2.78533E-2,2.911635E-3,1.5102403E-4,4.635754E-3,-8.327525E-4,-1.8321992E-3,3.3701137E-2,1.0917247E-3,1.4952005E-2,-5.6475406E-3,-1.903487E-2,1.8251343E-2,-3.1980358E-3,2.276254E-3,-3.150785E-3,7.6573305E-3,5.1056728E-2,2.1198563E-2,3.825867E-2],"categories":[],"categories_nodes":[],"categories_segments":[],"categories_sizes":[],"default_left":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":7,"left_children":[1,3,5,7,9,11,13,15,-1,17,19,21,23,25,27,29,31,33,35,-1,37,39,41,43,-1,45,47,49,51,53,-1,55,-1,57,59,61,63,65,67,69,71,-1,-1,-1,-1,73,-1,-1,75,-1,77,-1,79,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"loss_changes":[3.544874E-1,1.3708806E-1,1.2355733E-1,9.194523E-2,8.184896E-2,1.5205132E-2,7.3697E-2,3.2042325E-2,0E0,3.8669914E-2,1.940757E-2,1.6534833E-2,9.750228E-2,2.0155871E-2,4.514748E-2,7.72655E-3,2.8960137E-2,4.4908583E-2,2.2921113E-2,0E0,1.3433048E-2,1.8198038E-2,8.955989E-4,8.06209E-5,0E0,1.4330111E-2,3.783793E-2,6.193348E-2,1.08434856E-1,6.9764256E-3,0E0,4.1390672E-2,0E0,2.465333E-2,2.2079744E-3,6.9639683E-3,2.9146724E-4,4.684128E-3,2.1723492E-2,6.105366E-3,1.121588E-3,0E0,0E0,0E0,0E0,8.3589945E-3,0E0,0E0,4.883298E-4,0E0,6.0245126E-2,0E0,1.0556534E-2,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0],"parents":[2147483647,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,9,9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,20,20,21,21,22,22,23,23,25,25,26,26,27,27,28,28,29,29,31,31,33,33,34,34,35,35,36,36,37,37,38,38,39,39,40,40,45,45,48,48,50,50,52,52],"right_children":[2,4,6,8,10,12,14,16,-1,18,20,22,24,26,28,30,32,34,36,-1,38,40,42,44,-1,46,48,50,52,54,-1,56,-1,58,60,62,64,66,68,70,72,-1,-1,-1,-1,74,-1,-1,76,-1,78,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"split_conditions":[5.1E0,2.7E0,5.7E0,4.7E0,2.4E0,9E0,6.4E0,5.9E0,-8.941282E-2,3.7E0,1E-1,9.8E0,5E0,7.8E0,4E0,4.1E0,2.5E0,7E0,3.9E0,-2.7938051E-2,9.6E0,8.9E0,3.2E0,9.6E0,7.272416E-2,9E0,8.3E0,6.9E0,6.7E0,6E0,-1.4390945E-3,1.3E0,2.1097281E-4,6.5E0,1.9E0,8.2E0,5.5E0,4.4E0,7E0,9.8E0,1.9E0,6.352598E-3,2.826065E-2,-2.4670195E-3,2.1550656E-4,5.2E0,-2.3090018E-2,4.9884368E-2,5.4E0,-2.8568614E-2,6.2E0,8.847366E-2,9.5E0,-2.4386514E-2,-4.3862265E-2,-3.7657242E-2,4.2287037E-3,-3.845144E-2,-1.2018318E-2,1.4129878E-4,1.4309738E-2,6.851256E-5,-2.78533E-2,2.911635E-3,1.5102403E-4,4.635754E-3,-8.327525E-4,-1.8321992E-3,3.3701137E-2,1.0917247E-3,1.4952005E-2,-5.6475406E-3,-1.903487E-2,1.8251343E-2,-3.1980358E-3,2.276254E-3,-3.150785E-3,7.6573305E-3,5.1056728E-2,2.1198563E-2,3.825867E-2],"split_indices":[2,1,1,2,2,3,2,0,0,0,0,0,0,1,3,2,2,3,1,0,0,0,1,2,0,3,1,2,2,3,0,1,0,1,0,0,1,1,1,2,1,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"split_type":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sum_hessian":[3E2,1.5E2,1.5E2,4.1E1,1.09E2,8.5E1,6.5E1,4E1,1E0,3.7E1,7.2E1,7.7E1,8E0,1.6E1,4.9E1,2.2E1,1.8E1,2E1,1.7E1,1E0,7.1E1,7.5E1,2E0,7E0,1E0,1.1E1,5E0,1.7E1,3.2E1,2.1E1,1E0,8E0,1E1,1.7E1,3E0,4E0,1.3E1,6.6E1,5E0,7E1,5E0,1E0,1E0,6E0,1E0,9E0,2E0,1E0,4E0,2E0,1.5E1,2E0,3E1,1.7E1,4E0,5E0,3E0,1.1E1,6E0,2E0,1E0,1E0,3E0,6E0,7E0,1.9E1,4.7E1,3E0,2E0,6.8E1,2E0,2E0,3E0,1E0,8E0,2E0,2E0,1.2E1,3E0,2.4E1,6E0],"tree_param":{"num_deleted":"0","num_feature":"4","num_nodes":"81","size_leaf_vector":"1"}},{"base_weights":[7.475865E-5,-2.5765052E-2,2.5914075E-2,-6.272791E-2,-1.1417733E-2,7.224496E-3,4.9874526E-2,-5.1899992E-2,-7.600091E-2,-3.8820554E-2,3.0031577E-3,-2.1972712E-2,1.2256106E-2,7.295836E-3,6.335379E-2,-7.010276E-2,-2.7133487E-2,-4.897802E-2,1.315936E-2,-2.3747344E-2,5.2436963E-3,-1.0212508E-2,-1.7330768E-2,8.429636E-3,6.337547E-2,2.1700611E-2,-4.4944838E-2,3.7217077E-2,8.800182E-2,-7.291857E-2,-1.2232304E-3,-6.262084E-2,-1.2141295E-3,-7.5457506E-2,-1.7782435E-2,-3.5103562E-4,1.9125175E-2,1.8935798E-3,4.177938E-2,9.138394E-4,-1.8224034E-2,3.5036898E-3,4.616776E-2,-1.5939316E-2,3.6469534E-2,7.235636E-2,-5.7973205E-3,7.411212E-4,-1.8472016E-2,3.851E-3,9.0217695E-2,8.920976E-3,1.1250519E-1,-1.7477E-2,-3.333532E-2,-3.818664E-2,-5.955017E-3,-6.310061E-3,1.4559317E-3,2.5449635E-3,-2.6164545E-2,-1.6403034E-2,1.1875017E-3,1.1303426E-4,9.449554E-3,-5.540309E-4,3.9009722E-3,-1.4199526E-3,2.6960898E-2,-2.9695916E-3,-7.807506E-3,9.224871E-3,2.8870923E-5,-1.8326385E-3,6.181555E-2,1.83177E-4,-7.3558693E-3,-2.4320604E-3,3.861024E-2,2.531618E-4,-3.7821867E-3,-7.869291E-3,2.4388177E-2,4.218632E-2,1.8011767E-2,-2.2854889E-2,1.7459745E-2,7.637762E-2,2.098557E-2],"categories":[],"categories_nodes":[],"categories_segments":[],"categories_sizes":[],"default_left":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":8,"left_children":[1,3,5,7,9,11,13,15,-1,17,19,21,23,25,27,29,31,33,35,-1,37,39,-1,41,43,45,47,49,51,53,-1,55,57,59,61,-1,63,65,67,-1,69,71,73,75,-1,77,79,-1,-1,81,83,85,87,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"loss_changes":[2.0164026E-1,7.936182E-2,6.72591E-2,7.353535E-2,4.3585625E-2,1.29034715E-2,3.7417114E-2,1.6581431E-2,0E0,2.0707913E-2,1.3853271E-2,4.882696E-3,1.3940669E-2,1.3768088E-2,2.893576E-2,3.9787367E-3,1.7400347E-2,2.5091559E-2,6.2078773E-4,0E0,8.733599E-3,1.2239541E-3,0E0,1.2850961E-2,2.5014155E-2,1.9920466E-2,3.3058971E-3,4.5631565E-2,4.7440022E-2,5.8482364E-3,0E0,1.9600496E-2,1.5450661E-3,1.7517932E-2,1.3050213E-2,0E0,1.148033E-3,2.8081327E-3,1.38461795E-2,0E0,1.9701151E-4,5.8459016E-3,6.812424E-2,4.409782E-4,0E0,2.3711637E-2,4.629021E-4,0E0,0E0,4.173622E-2,6.768182E-3,3.379015E-2,9.70698E-2,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0],"parents":[2147483647,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,9,9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,20,20,21,21,23,23,24,24,25,25,26,26,27,27,28,28,29,29,31,31,32,32,33,33,34,34,36,36,37,37,38,38,40,40,41,41,42,42,43,43,45,45,46,46,49,49,50,50,51,51,52,52],"right_children":[2,4,6,8,10,12,14,16,-1,18,20,22,24,26,28,30,32,34,36,-1,38,40,-1,42,44,46,48,50,52,54,-1,56,58,60,62,-1,64,66,68,-1,70,72,74,76,-1,78,80,-1,-1,82,84,86,88,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"split_conditions":[5.1E0,2.7E0,5.7E0,4.7E0,2.4E0,1E0,6.4E0,5.9E0,-7.600091E-2,7E0,1E-1,8.5E0,9.8E0,9E0,4.7E0,4.1E0,8E-1,3.5E0,4.3E0,-2.3747344E-2,9.6E0,6.8E0,-1.7330768E-2,9E0,3E-1,5.5E0,4.3E0,8.8E0,3E0,6E0,-1.2232304E-3,8.2E0,1.9E0,9E-1,4.1E0,-3.5103562E-4,1.9E0,4.6E0,7E0,9.138394E-4,4.2E0,6E-1,5E0,2.9E0,3.6469534E-2,6E0,6E0,7.411212E-4,-1.8472016E-2,9.4E0,9.2E0,6.9E0,7E0,-1.7477E-2,-3.333532E-2,-3.818664E-2,-5.955017E-3,-6.310061E-3,1.4559317E-3,2.5449635E-3,-2.6164545E-2,-1.6403034E-2,1.1875017E-3,1.1303426E-4,9.449554E-3,-5.540309E-4,3.9009722E-3,-1.4199526E-3,2.6960898E-2,-2.9695916E-3,-7.807506E-3,9.224871E-3,2.8870923E-5,-1.8326385E-3,6.181555E-2,1.83177E-4,-7.3558693E-3,-2.4320604E-3,3.861024E-2,2.531618E-4,-3.7821867E-3,-7.869291E-3,2.4388177E-2,4.218632E-2,1.8011767E-2,-2.2854889E-2,1.7459745E-2,7.637762E-2,2.098557E-2],"split_indices":[2,1,1,2,2,3,2,0,0,3,0,2,2,3,0,2,1,0,1,0,0,0,0,3,1,2,0,1,3,3,0,0,2,0,1,0,0,2,1,0,1,0,0,0,0,3,3,0,0,2,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"split_type":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sum_hessian":[3E2,1.5E2,1.5E2,4.1E1,1.09E2,8.5E1,6.5E1,4E1,1E0,3.7E1,7.2E1,1.2E1,7.3E1,1.6E1,4.9E1,2.2E1,1.8E1,3.1E1,6E0,1E0,7.1E1,1E1,2E0,6.9E1,4E0,1.3E1,3E0,2.5E1,2.4E1,2.1E1,1E0,7E0,1.1E1,1.6E1,1.5E1,2E0,4E0,6.6E1,5E0,4E0,6E0,6.2E1,7E0,2E0,2E0,4E0,9E0,1E0,2E0,1.6E1,9E0,6E0,1.8E1,1.7E1,4E0,2E0,5E0,2E0,9E0,2E0,1.4E1,5E0,1E1,2E0,2E0,5E1,1.6E1,3E0,2E0,4E0,2E0,6E0,5.6E1,6E0,1E0,1E0,1E0,2E0,2E0,5E0,4E0,1.2E1,4E0,2E0,7E0,2E0,4E0,3E0,1.5E1],"tree_param":{"num_deleted":"0","num_feature":"4","num_nodes":"89","size_leaf_vector":"1"}},{"base_weights":[2.2983202E-4,-1.8118372E-2,2.1178989E-2,-4.5854762E-2,-7.6224455E-3,8.667869E-3,4.767676E-2,-1.437852E-1,-3.5182163E-2,-3.6866557E-2,-5.586936E-3,3.5428794E-3,5.838522E-2,1.4016402E-1,4.01154E-2,-3.7914782E-3,-5.498643E-2,-4.6863023E-2,-2.057411E-2,-1.2680641E-2,1.285403E-2,6.894572E-3,-4.39713E-2,8.570641E-3,3.5553236E-2,6.483835E-2,-1.764536E-3,-2.6269168E-2,4.5750972E-2,-5.146447E-2,-5.6572753E-4,-4.7018923E-2,9.7400945E-4,-3.3345204E-2,-3.0090506E-3,6.725669E-3,2.1568727E-2,-1.335282E-3,5.638497E-2,5.7449343E-4,-1.6059466E-2,-1.8743768E-2,4.2132974E-2,-2.8424265E-4,-3.4393907E-2,3.6610186E-2,4.066592E-2,-1.2164099E-2,-2.5499284E-2,-1.0397495E-3,7.8517204E-4,-2.351326E-2,3.5321163E-3,-3.0238234E-4,1.5569688E-3,-1.3275832E-2,3.6620202E-3,-2.0185241E-2,-2.2223644E-4,7.93923E-3,-4.021302E-5,1.2387042E-3,-6.3179703E-3,-1.0216078E-3,5.3045094E-2,-7.370055E-4,-1.0140753E-2,2.521232E-2,-6.2524797E-3,-1.301483E-2,-2.462429E-3,-1.0156602E-3,1.4173096E-2],"categories":[],"categories_nodes":[],"categories_segments":[],"categories_sizes":[],"default_left":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":9,"left_children":[1,3,5,7,9,11,13,15,17,-1,19,21,23,25,27,-1,-1,29,31,33,35,37,39,41,-1,-1,-1,43,45,47,49,51,53,55,57,59,-1,61,63,-1,-1,65,67,-1,69,-1,71,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"loss_changes":[1.1608194E-1,4.6520643E-2,4.6330623E-2,4.0928893E-2,2.6999194E-2,2.4508832E-2,2.58471E-2,1.8406287E-2,6.031856E-3,0E0,1.54683255E-2,1.4476676E-2,2.5940454E-2,3.45538E-2,1.7288782E-2,0E0,0E0,4.657645E-3,1.1442516E-2,1.6887695E-2,1.1456812E-2,3.4288384E-2,2.7346173E-3,6.290143E-3,0E0,0E0,0E0,7.9034106E-4,1.0553487E-2,3.8755424E-3,3.676372E-5,1.7515698E-2,1.0549924E-4,1.3955422E-2,8.551931E-3,4.2009456E-3,0E0,7.954981E-3,8.701016E-2,0E0,0E0,8.9800975E-4,9.66898E-3,0E0,3.5005156E-4,0E0,1.1460528E-2,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0,0E0],"parents":[2147483647,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,10,10,11,11,12,12,13,13,14,14,17,17,18,18,19,19,20,20,21,21,22,22,23,23,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,35,35,37,37,38,38,41,41,42,42,44,44,46,46],"right_children":[2,4,6,8,10,12,14,16,18,-1,20,22,24,26,28,-1,-1,30,32,34,36,38,40,42,-1,-1,-1,44,46,48,50,52,54,56,58,60,-1,62,64,-1,-1,66,68,-1,70,-1,72,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"split_conditions":[5.4E0,2.7E0,6.9E0,8E-1,1E-1,9.7E0,7E0,5E-1,5.9E0,-3.6866557E-2,8E0,8.9E0,3.1E0,5.3E0,1E0,-3.7914782E-3,-5.498643E-2,4.1E0,2.5E0,2.4E0,9.5E0,7.6E0,1.9E0,5.9E0,3.5553236E-2,6.483835E-2,-1.764536E-3,2.6E0,5.5E0,3.2E0,2.2E0,1.3E0,9.2E0,7E0,1E-1,8.5E0,2.1568727E-2,5.1E0,4.9E0,5.7449343E-4,-1.6059466E-2,2.9E0,6.9E0,-2.8424265E-4,5.3E0,3.6610186E-2,6.4E0,-1.2164099E-2,-2.5499284E-2,-1.0397495E-3,7.8517204E-4,-2.351326E-2,3.5321163E-3,-3.0238234E-4,1.5569688E-3,-1.3275832E-2,3.6620202E-3,-2.0185241E-2,-2.2223644E-4,7.93923E-3,-4.021302E-5,1.2387042E-3,-6.3179703E-3,-1.0216078E-3,5.3045094E-2,-7.370055E-4,-1.0140753E-2,2.521232E-2,-6.2524797E-3,-1.301483E-2,-2.462429E-3,-1.0156602E-3,1.4173096E-2],"split_indices":[2,1,1,0,2,2,1,0,0,0,1,0,1,0,3,0,0,2,2,2,0,0,1,0,0,0,0,0,2,2,0,1,0,3,0,1,0,1,1,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"split_type":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"sum_hessian":[3E2,1.6E2,1.4E2,4.3E1,1.17E2,9.6E1,4.4E1,3E0,4E1,1E0,1.16E2,8.8E1,8E0,2E0,4.2E1,1E0,2E0,2.1E1,1.9E1,8.4E1,3.2E1,8.3E1,5E0,5E0,3E0,1E0,1E0,3E0,3.9E1,1.9E1,2E0,8E0,1.1E1,2.6E1,5.8E1,3E1,2E0,7.2E1,1.1E1,1E0,4E0,3E0,2E0,1E0,2E0,1E0,3.8E1,1.6E1,3E0,1E0,1E0,5E0,3E0,8E0,3E0,2.1E1,5E0,1E0,5.7E1,7E0,2.3E1,5.7E1,1.5E1,8E0,3E0,2E0,1E0,1E0,1E0,1E0,1E0,5E0,3.3E1],"tree_param":{"num_deleted":"0","num_feature":"4","num_nodes":"73","size_leaf_vector":"1"}}]},"name":"gbtree"},"learner_model_param":{"base_score":"[9.9333334E-1]","boost_from_average":"1","num_class":"0","num_feature":"4","num_target":"1"},"objective":{"name":"reg:squarederror","reg_loss_param":{"scale_pos_weight":"1"}}},"version":[3,1,0]} --------------------------------------------------------------------------------