Xgboost: [R] Indeks iterasi terbaik dari penghentian awal dibuang ketika model disimpan ke disk

Dibuat pada 15 Jan 2020  ·  33Komentar  ·  Sumber: dmlc/xgboost

Nilai-nilai ini diprediksi setelah xgboost::xgb.train :
247367.2 258693.3 149572.2 201675.8 250493.9 292349.2 414828.0 296503.2 260851.9 190413.3

Nilai-nilai ini diprediksi setelah xgboost::xgb.save dan xgboost::xgb.load dari model sebelumnya:
247508.8 258658.2 149252.1 201692.6 250458.1 292313.4 414787.2 296462.5 260879.0 190430.1

Mereka dekat, tapi tidak sama. Perbedaan antara dua prediksi ini berkisar dari -1317.094 hingga 1088.859 pada kumpulan 25k sampel. Jika dibandingkan dengan true label, maka MAE/RMSE kedua prediksi ini tidak berbeda jauh.

Jadi saya menduga ini ada hubungannya dengan kesalahan pembulatan selama memuat/menyimpan karena MAE/RMSE tidak jauh berbeda. Tetap saja, saya merasa ini aneh karena penyimpanan biner model seharusnya tidak menimbulkan kesalahan pembulatan?

Ada yang punya petunjuk?

PS Mengunggah dan mendokumentasikan proses pelatihan tampaknya tidak penting bagi saya di sini. Saya bisa memberikan detail jika perlu, atau membuat simulasi dengan data dummy untuk membuktikan maksudnya.

Blocking bug

Komentar yang paling membantu

Misteri terpecahkan. Saya mengidentifikasi penyebab sebenarnya. Ketika model disimpan ke disk, informasi tentang penghentian awal akan dibuang. Dalam contoh, XGBoost menjalankan 6381 putaran boosting dan menemukan model terbaik di 6378 putaran. Objek model dalam memori berisi 6381 pohon, bukan 6378 pohon, karena tidak ada pohon yang dihapus. Ada bidang tambahan best_iteration yang mengingat iterasi mana yang terbaik:

> fit$best_iteration
[1] 6378

Bidang tambahan ini secara diam-diam dibuang saat kita menyimpan model ke disk. Jadi predict() dengan model asli menggunakan 6378 pohon, sedangkan predict() dengan model yang dipulihkan menggunakan 6381 pohon.

> x <- predict(fit, newdata = dtrain2, predleaf = TRUE)
> x2 <- predict(fit.loaded, newdata = dtrain2, predleaf = TRUE)
> dim(x)
[1] 5000 6378
> dim(x2)
[1] 5000 6381

Semua 33 komentar

Seharusnya tidak ada kesalahan pembulatan untuk biner atau json. Apakah Anda menggunakan panah?

Tidak:

params <- list(objective = 'reg:squarederror',
               max_depth = 10, eta = 0.02, subsammple = 0.5,
               base_score = median(xgboost::getinfo(xgb.train, 'label'))
)

xgboost::xgb.train(
  params = params, data = xgb.train,
  watchlist = list('train' = xgb.train, 'test' = xgb.test),
  nrounds = 10000, verbose = TRUE, print_every_n = 25,
  eval_metric = 'mae',
  early_stopping_rounds = 3, maximize = FALSE)

Bisakah Anda memberi kami data dummy di mana fenomena ini terjadi?

Ini dia (Cepat & Kotor):

N <- 100000
set.seed(2020)
X <- data.frame('X1' = rnorm(N), 'X2' = runif(N), 'X3' = rpois(N, lambda = 1))
Y <- with(X, X1 + X2 - X3 + X1*X2^2 - ifelse(X1 > 0, 2, X3))

params <- list(objective = 'reg:squarederror',
               max_depth = 5, eta = 0.02, subsammple = 0.5,
               base_score = median(Y)
)

dtrain <- xgboost::xgb.DMatrix(data = data.matrix(X), label = Y)

fit <- xgboost::xgb.train(
  params = params, data = dtrain,
  watchlist = list('train' = dtrain),
  nrounds = 10000, verbose = TRUE, print_every_n = 25,
  eval_metric = 'mae',
  early_stopping_rounds = 3, maximize = FALSE
)

pred <- stats::predict(fit, newdata = dtrain)

xgboost::xgb.save(fit, 'booster.raw')
fit.loaded <- xgboost::xgb.load('booster.raw')

pred.loaded <- stats::predict(fit.loaded, newdata = dtrain)

identical(pred, pred.loaded)
pred[1:10]
pred.loaded[1:10]

sqrt(mean((Y - pred)^2))
sqrt(mean((Y - pred.loaded)^2))

Di mesin saya, identical(pred, pred.loaded) adalah FALSE (yaitu seharusnya TRUE). Berikut adalah output dari perintah terakhir:

> identical(pred, pred.loaded)
[1] FALSE
> pred[1:10]
 [1] -4.7971768 -2.5070562 -0.8889422 -4.9199696 -4.4374819 -0.2739395 -0.9825708  0.4579227  1.3667605 -4.3333349
> pred.loaded[1:10]
 [1] -4.7971768 -2.5070562 -0.8889424 -4.9199696 -4.4373770 -0.2739397 -0.9825710  0.4579227  1.3667605 -4.3333349
> 
> sqrt(mean((Y - pred)^2))
[1] 0.02890702
> sqrt(mean((Y - pred.loaded)^2))
[1] 0.02890565

Anda melihat bahwa prediksi terkadang sedikit berbeda. Bisakah Anda menjalankan kembali contoh kode pada mesin Anda dan melihat apakah mesin tersebut memiliki masalah yang sama?

Beberapa info tambahan tentang R dan xgboost:

> sessionInfo()
R version 3.6.1 (2019-07-05)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows >= 8 x64 (build 9200)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252    LC_MONETARY=English_United States.1252 LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] compiler_3.6.1    magrittr_1.5      Matrix_1.2-17     tools_3.6.1       yaml_2.2.0        xgboost_0.90.0.2  stringi_1.4.3     grid_3.6.1       
 [9] data.table_1.12.4 lattice_0.20-38 

Perhatikan juga bahwa:

> identical(fit$raw, fit.loaded$raw)
[1] TRUE

Terima kasih untuk skripnya. Hanya pembaruan, saya menjalankannya dengan menyimpan ke json dan file biner dengan:

xgboost::xgb.save(fit, 'booster.json')
fit.loaded <- xgboost::xgb.load('booster.json')

xgboost::xgb.save(fit.loaded, 'booster-1.json')

Nilai hash (via sha256sum ./booster.json ) dari booster.json dan booster-1.json persis sama, jadi tebakan saya adalah di suatu tempat ada perbedaan yang disebabkan oleh aritmatika floating point.

Mengapa menutup masalah tanpa mengetahui penyebabnya?

@trivialfis Apakah Anda mendapatkan True untuk identical(pred, pred.loaded) ? OP bertanya mengapa prediksi tidak cocok, meskipun dua model memiliki tanda tangan biner yang sama.

Saya akan mencoba mereproduksinya sendiri.

Oh maaf. Penyebab yang saya temukan adalah cache prediksi. Setelah memuat model, nilai prediksi berasal dari prediksi yang sebenarnya, bukan nilai yang di-cache:

jadi tebakan saya adalah di suatu tempat ada perbedaan yang disebabkan oleh aritmatika floating point.

Jadi cache prediksi berinteraksi dengan aritmatika floating-point dengan cara yang merusak?

@ hcho3 Ini adalah masalah yang saya temukan selama menerapkan metode pengawetan baru. Saya percaya itu memainkan peran utama di sini. Jadi pertama-tama kurangi jumlah pohon menjadi 1000 (yang masih cukup besar dan seharusnya cukup untuk demo).

Buat ulang DMatrix sebelum prediksi untuk menyingkirkan cache:

dtrain_2 <- xgboost::xgb.DMatrix(data = data.matrix(X), label = Y)

pred <- stats::predict(fit, newdata = dtrain_2)

Ini akan lulus ujian identical . Jika tidak, gagal.

Mendapatkan lebih banyak pohon, tes yang sama masih memiliki perbedaan kecil (1e-7 untuk 2000 pohon). Tetapi apakah kita perlu menghasilkan sedikit demi sedikit hasil yang identik bahkan di lingkungan multi-utas?

Karena penjumlahan floating point tidak asosiatif, kita dapat menjadikannya sebagai item yang harus dilakukan untuk memiliki jaminan yang kuat untuk urutan perhitungan, jika diinginkan.

Sebenarnya membuat jaminan yang kuat untuk pesanan tidak akan berhasil (akan banyak membantu tetapi masih akan ada perbedaan). Titik mengambang dalam register FPU CPU dapat memiliki presisi yang lebih tinggi kemudian disimpan kembali ke memori. (Implementasi perangkat keras dapat menggunakan presisi yang lebih tinggi untuk nilai intermedia, https://en.wikipedia.org/wiki/Extended_precision). Maksud saya adalah ketika hasil untuk 1000 pohon benar-benar dapat direproduksi dalam float 32 bit, itu bukan bug pemrograman.

Saya setuju bahwa penjumlahan floating-point tidak asosiatif. Saya akan menjalankan skrip sendiri dan melihat apakah perbedaannya cukup kecil untuk dikaitkan dengan aritmatika titik-mengambang.

Secara umum, saya biasanya menggunakan np.testing.assert_almost_equal dengan decimal=5 untuk menguji apakah dua array float hampir sama satu sama lain.

Ya. Maaf untuk menutup tanpa catatan rinci.

@hcho3 Ada pembaruan?

Saya belum mengatasinya. Biarkan saya melihat minggu ini.

@trivialfis saya berhasil mereproduksi bug. Saya menjalankan skrip yang disediakan dan mendapatkan FALSE untuk identical(pred, pred.loaded) . Saya mencoba membuat DMatrix baru dtrain_2 seperti yang Anda sarankan dan masih mendapatkan FALSE untuk pengujian.

Output dari skrip @DavorJ :

[1] FALSE     # identical(pred, pred.loaded)
 [1] -4.7760534 -2.5083885 -0.8860036 -4.9163256 -4.4455137 -0.2548684
 [7] -0.9745615  0.4646015  1.3602829 -4.3288369     # pred[1:10]
 [1] -4.7760534 -2.5083888 -0.8860038 -4.9163256 -4.4454765 -0.2548686
 [7] -0.9745617  0.4646015  1.3602829 -4.3288369     # pred.loaded[1:10]
[1] 0.02456085   # MSE on pred
[1] 0.02455945   # MSE on pred.loaded

Keluaran dari skrip yang dimodifikasi, dengan dtrain_2 <- xgboost::xgb.DMatrix(data = data.matrix(X), label = Y) :

[1] FALSE     # identical(pred, pred.loaded)
 [1] -4.7760534 -2.5083885 -0.8860036 -4.9163256 -4.4455137 -0.2548684
 [7] -0.9745615  0.4646015  1.3602829 -4.3288369     # pred[1:10]
 [1] -4.7760534 -2.5083888 -0.8860038 -4.9163256 -4.4454765 -0.2548686
 [7] -0.9745617  0.4646015  1.3602829 -4.3288369     # pred.loaded[1:10]
[1] 0.02456085   # MSE on pred
[1] 0.02455945   # MSE on pred.loaded

Jadi sesuatu yang lain harus terjadi.

Saya juga mencoba menjalankan tes pulang pergi:

xgboost::xgb.save(fit, 'booster.raw')
fit.loaded <- xgboost::xgb.load('booster.raw')
xgboost::xgb.save(fit.loaded, 'booster.raw.roundtrip')

dan dua file biner booster.raw dan booster.raw.roundtrip identik.

Perbedaan maksimum antara pred dan pred.loaded adalah 0,0008370876.

Contoh kecil yang berjalan lebih cepat:

library(xgboost)

N <- 5000
set.seed(2020)
X <- data.frame('X1' = rnorm(N), 'X2' = runif(N), 'X3' = rpois(N, lambda = 1))
Y <- with(X, X1 + X2 - X3 + X1*X2^2 - ifelse(X1 > 0, 2, X3))

params <- list(objective = 'reg:squarederror',
               max_depth = 5, eta = 0.02, subsammple = 0.5,
               base_score = median(Y)
)

dtrain <- xgboost::xgb.DMatrix(data = data.matrix(X), label = Y)

fit <- xgboost::xgb.train(
  params = params, data = dtrain,
  watchlist = list('train' = dtrain),
  nrounds = 10000, verbose = TRUE, print_every_n = 25,
  eval_metric = 'mae',
  early_stopping_rounds = 3, maximize = FALSE
)

pred <- stats::predict(fit, newdata = dtrain)

invisible(xgboost::xgb.save(fit, 'booster.raw'))
fit.loaded <- xgboost::xgb.load('booster.raw')
invisible(xgboost::xgb.save(fit.loaded, 'booster.raw.roundtrip'))

pred.loaded <- stats::predict(fit.loaded, newdata = dtrain)

identical(pred, pred.loaded)
pred[1:10]
pred.loaded[1:10]
max(abs(pred - pred.loaded))

sqrt(mean((Y - pred)^2))
sqrt(mean((Y - pred.loaded)^2))

Keluaran:

[1] FALSE
 [1] -2.4875379 -0.9452241 -6.9658904 -2.9985323 -4.2192593 -0.8505422
 [7] -0.3928839 -1.6886091 -1.3611379 -3.1278882
 [1] -2.4875379 -0.9452239 -6.9658904 -2.9985323 -4.2192593 -0.8505420
 [7] -0.3928837 -1.6886090 -1.3611377 -3.1278882
[1] 0.0001592636
[1] 0.01370754
[1] 0.01370706

Baru saja mencoba melakukan satu perjalanan pulang pergi ekstra, dan sekarang prediksi tidak berubah lagi.

library(xgboost)

N <- 5000
set.seed(2020)
X <- data.frame('X1' = rnorm(N), 'X2' = runif(N), 'X3' = rpois(N, lambda = 1))
Y <- with(X, X1 + X2 - X3 + X1*X2^2 - ifelse(X1 > 0, 2, X3))

params <- list(objective = 'reg:squarederror',
               max_depth = 5, eta = 0.02, subsammple = 0.5,
               base_score = median(Y)
)

dtrain <- xgboost::xgb.DMatrix(data = data.matrix(X), label = Y)

fit <- xgboost::xgb.train(
  params = params, data = dtrain,
  watchlist = list('train' = dtrain),
  nrounds = 10000, verbose = TRUE, print_every_n = 25,
  eval_metric = 'mae',
  early_stopping_rounds = 3, maximize = FALSE
)

pred <- stats::predict(fit, newdata = dtrain)

invisible(xgboost::xgb.save(fit, 'booster.raw'))
fit.loaded <- xgboost::xgb.load('booster.raw')
invisible(xgboost::xgb.save(fit.loaded, 'booster.raw.roundtrip'))
fit.loaded2 <- xgboost::xgb.load('booster.raw.roundtrip')

pred.loaded <- stats::predict(fit.loaded, newdata = dtrain)
pred.loaded2 <- stats::predict(fit.loaded2, newdata = dtrain)

identical(pred, pred.loaded)
identical(pred.loaded, pred.loaded2)
pred[1:10]
pred.loaded[1:10]
pred.loaded2[1:10]
max(abs(pred - pred.loaded))
max(abs(pred.loaded - pred.loaded2))

sqrt(mean((Y - pred)^2))
sqrt(mean((Y - pred.loaded)^2))
sqrt(mean((Y - pred.loaded2)^2))

Hasil:

[1] FALSE
[1] TRUE
 [1] -2.4875379 -0.9452241 -6.9658904 -2.9985323 -4.2192593 -0.8505422
 [7] -0.3928839 -1.6886091 -1.3611379 -3.1278882
 [1] -2.4875379 -0.9452239 -6.9658904 -2.9985323 -4.2192593 -0.8505420
 [7] -0.3928837 -1.6886090 -1.3611377 -3.1278882
 [1] -2.4875379 -0.9452239 -6.9658904 -2.9985323 -4.2192593 -0.8505420
 [7] -0.3928837 -1.6886090 -1.3611377 -3.1278882
[1] 0.0001592636
[1] 0
[1] 0.01370754
[1] 0.01370706
[1] 0.01370706

Jadi mungkin cache prediksi memang bermasalah.

Saya menjalankan ulang skrip dengan caching prediksi dinonaktifkan:

diff --git a/src/predictor/cpu_predictor.cc b/src/predictor/cpu_predictor.cc
index ebc15128..c40309bc 100644
--- a/src/predictor/cpu_predictor.cc
+++ b/src/predictor/cpu_predictor.cc
@@ -259,7 +259,7 @@ class CPUPredictor : public Predictor {
     // delta means {size of forest} * {number of newly accumulated layers}
     uint32_t delta = end_version - beg_version;
     CHECK_LE(delta, model.trees.size());
-    predts->Update(delta);
+    //predts->Update(delta);

     CHECK(out_preds->Size() == output_groups * dmat->Info().num_row_ ||
           out_preds->Size() == dmat->Info().num_row_);

(Menonaktifkan cache prediksi menghasilkan pelatihan yang sangat lambat.)

Keluaran:

[1] FALSE
[1] TRUE
 [1] -2.4908853 -0.9507379 -6.9615889 -2.9935317 -4.2165089 -0.8543566
 [7] -0.3940181 -1.6930715 -1.3572118 -3.1403396
 [1] -2.4908853 -0.9507380 -6.9615889 -2.9935317 -4.2165089 -0.8543567
 [7] -0.3940183 -1.6930716 -1.3572119 -3.1403399
 [1] -2.4908853 -0.9507380 -6.9615889 -2.9935317 -4.2165089 -0.8543567
 [7] -0.3940183 -1.6930716 -1.3572119 -3.1403399
[1] 0.0001471043
[1] 0
[1] 0.01284297
[1] 0.01284252
[1] 0.01284252

Jadi cache prediksi jelas BUKAN penyebab bug ini.

Prediksi daun juga berbeda:

invisible(xgboost::xgb.save(fit, 'booster.raw'))
fit.loaded <- xgboost::xgb.load('booster.raw')
invisible(xgboost::xgb.save(fit.loaded, 'booster.raw.roundtrip'))
fit.loaded2 <- xgboost::xgb.load('booster.raw.roundtrip')

x <- predict(fit, newdata = dtrain2, predleaf = TRUE)
x2 <- predict(fit.loaded, newdata = dtrain2, predleaf = TRUE)
x3 <- predict(fit.loaded2, newdata = dtrain2, predleaf = TRUE)

identical(x, x2)
identical(x2, x3)

Keluaran:

[1] FALSE
[1] TRUE

Misteri terpecahkan. Saya mengidentifikasi penyebab sebenarnya. Ketika model disimpan ke disk, informasi tentang penghentian awal akan dibuang. Dalam contoh, XGBoost menjalankan 6381 putaran boosting dan menemukan model terbaik di 6378 putaran. Objek model dalam memori berisi 6381 pohon, bukan 6378 pohon, karena tidak ada pohon yang dihapus. Ada bidang tambahan best_iteration yang mengingat iterasi mana yang terbaik:

> fit$best_iteration
[1] 6378

Bidang tambahan ini secara diam-diam dibuang saat kita menyimpan model ke disk. Jadi predict() dengan model asli menggunakan 6378 pohon, sedangkan predict() dengan model yang dipulihkan menggunakan 6381 pohon.

> x <- predict(fit, newdata = dtrain2, predleaf = TRUE)
> x2 <- predict(fit.loaded, newdata = dtrain2, predleaf = TRUE)
> dim(x)
[1] 5000 6378
> dim(x2)
[1] 5000 6381

@trivialfis Saya cenderung secara fisik menghapus pohon. Jika pelatihan berhenti pada 6381 putaran dan iterasi terbaik adalah pada 6378 putaran, pengguna akan mengharapkan model akhir memiliki 6378 pohon.

@ hcho3 Saya pikir ini masalah serupa di https://github.com/dmlc/xgboost/issues/4052 .

bset_iteration harus disimpan ke Learner::attributes_ , yang dapat diakses melalui xgboost::xgb.attr .

@hcho3 ,

Perhatikan juga dokumentasi xgboost:::predict.xgb.Booster() :

image

Jika saya mengerti dengan benar, dokumentasi tidak sepenuhnya benar? Berdasarkan dokumentasi, saya mengharapkan bahwa prediksi sudah menggunakan semua pohon. Sayangnya saya belum memverifikasi ini.

@DavorJ Ketika penghentian awal diaktifkan, predict() akan menggunakan bidang best_iteration untuk mendapatkan prediksi.

@trivialfis Situasi lebih buruk di sisi Python, karena xgb.predict() tidak akan menggunakan informasi dari penghentian awal sama sekali:

import xgboost as xgb
import numpy as np
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split

X, y = load_boston(return_X_y=True)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)

params = {'objective': 'reg:squarederror'}

bst = xgb.train(params, dtrain, 100, [(dtrain, 'train'), (dtest, 'test')],
                early_stopping_rounds=5)

x = bst.predict(dtrain, pred_leaf=True)
x2 = bst.predict(dtrain, pred_leaf=True, ntree_limit=bst.best_iteration)
print(x.shape)
print(x2.shape)

pred = bst.predict(dtrain)
pred2 = bst.predict(dtrain, ntree_limit=bst.best_iteration)

print(np.max(np.abs(pred - pred2)))

Keluaran:

Will train until test-rmse hasn't improved in 5 rounds.
[1]     train-rmse:12.50316     test-rmse:11.92709
...
[25]    train-rmse:0.56720      test-rmse:2.56874
[26]    train-rmse:0.54151      test-rmse:2.56722
[27]    train-rmse:0.51842      test-rmse:2.56124
[28]    train-rmse:0.47489      test-rmse:2.56640
[29]    train-rmse:0.45489      test-rmse:2.58780
[30]    train-rmse:0.43093      test-rmse:2.59385
[31]    train-rmse:0.41865      test-rmse:2.59364
[32]    train-rmse:0.40823      test-rmse:2.59465
Stopping. Best iteration:
[27]    train-rmse:0.51842      test-rmse:2.56124
(404, 33)
(404, 27)
0.81269073

Pengguna harus ingat untuk mengambil bst.best_iteration dan meneruskannya sebagai argumen ntree_limit saat memanggil predict() . Ini rawan kesalahan dan membuat kejutan yang tidak menyenangkan.

Kami memiliki dua opsi untuk perbaikan:

  1. Hapus secara fisik pohon yang melewati best_iteration .
  2. Simpan informasi best_iteration saat membuat serial model, dan gunakan fungsi predict() .

@hcho3 Saya punya ide setengah matang tentang ini, yang juga terkait dengan opsi process_type = update , dan forest.

Latar belakang

Untuk rangkuman singkat dari masalah yang kita miliki dengan update , jika num_boost_round digunakan dengan update lebih kecil dari jumlah pohon yang sudah ada, pohon yang tidak diperbarui akan dihapus .

Untuk pengenalan singkat tentang masalah hutan, best_iteration tidak berlaku untuk hutan karena fungsi predict memerlukan jumlah pohon tertentu alih-alih iterasi, jadi di Python ada sesuatu yang disebut best_ntree_limit , yang sangat membingungkan saya. Saya secara eksplisit mengganti ntree_limit di inplace_predict dengan iteration_range untuk menghindari atribut ini.

Ide

Saya ingin menambahkan metode slice dan concat ke booster , yang mengekstrak pohon menjadi 2 model dan menggabungkan pohon dari 2 model menjadi 1. Jika kita memiliki 2 metode ini :

  • base_margin_ tidak lagi diperlukan dan saya yakin ini lebih intuitif untuk pengguna lain.
  • ntree_limit dalam prediksi tidak lagi diperlukan, kita cukup mengiris model dan menjalankan prediksi pada irisan.
  • update proses mandiri, cukup perbarui pohon dalam irisan sekaligus, tidak num_boost_rounds .

Lebih jauh

Saya juga percaya ini entah bagaimana terhubung ke pohon multi target. Seolah-olah kita dapat mendukung pohon multi-target multi-kelas di masa depan, akan ada banyak cara untuk mengatur pohon, seperti menggunakan output_groups untuk setiap kelas, atau setiap target, memasangkan dengan daun hutan dan vektor. ntree_limit tidak akan cukup.

Juga #5531 .

Tapi idenya cukup awal jadi saya tidak memiliki kepercayaan diri untuk membagikannya, sekarang kita sedang dalam masalah ini, mungkin saya bisa mendapatkan beberapa masukan tentang ini.

Mengingat timeline 1.1, dapatkah kami memperluas dokumentasi untuk mengklarifikasi bagaimana pengguna perlu menangkap dan menggunakan iterasi terbaik ini dalam prediksi secara manual?
Dan tambahkan ke masalah yang diketahui di catatan rilis?

@trivialfis terdengar menarik, selama kita tidak memperumit masalah konfigurasi lebih lanjut dengan melakukan ini.

Menghapus pohon tambahan dari model seperti yang disarankan oleh @ hcho3 menarik karena kita tidak harus berurusan dengan inkonsistensi apa pun dari memiliki panjang model aktual dan panjang model teoretis pada saat yang bersamaan.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat