Xgboost: Bagaimana cara XGBoost menghasilkan probabilitas kelas dalam masalah klasifikasi multikelas?

Dibuat pada 8 Nov 2016  ·  15Komentar  ·  Sumber: dmlc/xgboost

Ketika saya mencoba membuang pohon XGBoost terlatih, saya mendapatkannya dalam format berikut:

0:[f37<39811] 
    1:[f52<199.5] 
        3:leaf=-0.021461
        4:[f2<0.00617284] 
            9:leaf=-0.0118755
            10:[f35<-83.548] 
                19:[f13<0.693844] 
                    23:[f37<1831]
                        29:leaf=-0
                        30:leaf=0.123949
                    24:leaf=0.0108628
                20:[f35<-74.6198] 
                    25:leaf=-0.0175897
                    26:leaf=0.051898
    2:leaf=-0.0239901

Meskipun jelas apa struktur pohonnya, tidak jelas bagaimana menginterpretasikan nilai daun. Untuk masalah klasifikasi _ biner _ dan fungsi biaya kerugian log, nilai daun dapat dikonversi ke probabilitas kelas menggunakan fungsi logistik: 1/(1+exp(nilai)). Namun, untuk masalah klasifikasi _multiclass_ , tidak ada informasi tentang kelas mana nilai-nilai itu berasal, dan tanpa informasi itu, tidak jelas bagaimana menghitung probabilitas kelas.

Ada ide? Atau adakah fungsi lain untuk mengeluarkan informasi tersebut dari pohon yang dilatih?

Komentar yang paling membantu

Itu pengamatan yang sangat bagus, yang tidak saya perhatikan sebelumnya. Benar, jumlah pohon dalam model adalah n_estimator x n_class . Namun, untuk mengetahui urutan pohon, saya menggunakan contoh mainan berikut:

x = np.concatenate([np.ones([10,1])*np.array([1,0,0]),np.ones([10,1])*np.array([0,1,0]),np.ones([10,1])*np.array([0,0,1])], axis=0)
y = np.array([['a']*10+['c']*10+['b']*10]).reshape([30,1])
model = xgb.XGBClassifier(n_estimators=2, objective='mlogloss').fit(x, y)
model.booster().dump_model('trees.txt')

yang pada dasarnya membuat dataset pelatihan di mana [1,0,0] dipetakan ke 'a', [0,1,0] dipetakan ke 'c', dan [0,0,1] dipetakan ke 'b'. Saya sengaja mengganti urutan 'b' dan 'c' untuk melihat apakah xgboost mengurutkan kelas berdasarkan labelnya sebelum membuang pohon.

Berikut adalah model dump yang dihasilkan:

booster[0]:
0:[f0<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[1]:
0:[f2<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[2]:
0:[f1<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[3]:
0:[f0<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941
booster[4]:
0:[f2<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941
booster[5]:
0:[f1<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941

dan berikut kesimpulannya :

  1. Seperti yang Anda sebutkan, meskipun n_estimator =2, jumlah pohonnya adalah 2x3=6.
  2. Melihat urutan fitur yang digunakan pohon, tampaknya pohon pertama termasuk kelas pertama, pohon kedua termasuk kelas kedua, dan seterusnya, hingga kelas terakhir. Kemudian, pola yang sama diulang sampai semua estimator tertutup.
  3. Urutan kelas tampaknya konsisten dengan apa yang numpy.unique() kembali - ini dia alfabet, dan itulah mengapa pohon pertama menggunakan f0 sedangkan yang kedua menggunakan f2 .

Alangkah baiknya jika informasi ini ditambahkan ke dokumentasi xgboost.

Semua 15 komentar

Saya juga punya pertanyaan yang sama. Satu-satunya hal yang saya perhatikan adalah jika Anda memiliki 20 kelas dan menetapkan jumlah penaksir menjadi 100, maka Anda akan memiliki 20*100=2000 pohon yang dicetak dalam model Anda. Sekarang tebakan saya adalah bahwa 100 penduga pertama mengklasifikasikan kelas satu vs yang lain. 100 penduga berikutnya mengklasifikasikan kelas kedua vs yang lain, dll.

Tidak bisa memastikan tapi mungkin bisa seperti ini?

Itu pengamatan yang sangat bagus, yang tidak saya perhatikan sebelumnya. Benar, jumlah pohon dalam model adalah n_estimator x n_class . Namun, untuk mengetahui urutan pohon, saya menggunakan contoh mainan berikut:

x = np.concatenate([np.ones([10,1])*np.array([1,0,0]),np.ones([10,1])*np.array([0,1,0]),np.ones([10,1])*np.array([0,0,1])], axis=0)
y = np.array([['a']*10+['c']*10+['b']*10]).reshape([30,1])
model = xgb.XGBClassifier(n_estimators=2, objective='mlogloss').fit(x, y)
model.booster().dump_model('trees.txt')

yang pada dasarnya membuat dataset pelatihan di mana [1,0,0] dipetakan ke 'a', [0,1,0] dipetakan ke 'c', dan [0,0,1] dipetakan ke 'b'. Saya sengaja mengganti urutan 'b' dan 'c' untuk melihat apakah xgboost mengurutkan kelas berdasarkan labelnya sebelum membuang pohon.

Berikut adalah model dump yang dihasilkan:

booster[0]:
0:[f0<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[1]:
0:[f2<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[2]:
0:[f1<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[3]:
0:[f0<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941
booster[4]:
0:[f2<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941
booster[5]:
0:[f1<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941

dan berikut kesimpulannya :

  1. Seperti yang Anda sebutkan, meskipun n_estimator =2, jumlah pohonnya adalah 2x3=6.
  2. Melihat urutan fitur yang digunakan pohon, tampaknya pohon pertama termasuk kelas pertama, pohon kedua termasuk kelas kedua, dan seterusnya, hingga kelas terakhir. Kemudian, pola yang sama diulang sampai semua estimator tertutup.
  3. Urutan kelas tampaknya konsisten dengan apa yang numpy.unique() kembali - ini dia alfabet, dan itulah mengapa pohon pertama menggunakan f0 sedangkan yang kedua menggunakan f2 .

Alangkah baiknya jika informasi ini ditambahkan ke dokumentasi xgboost.

Saya menutup masalah ini sekarang.

Saya masih punya komentar di sini. Saya setuju dengan bagaimana pohon disusun dalam kasus multi-kelas. Tapi bagaimana nilai daun diubah menjadi probabilitas?

Sepertinya untuk kasus biner Anda harus menerapkan 1/(1+exp(nilai)), sedangkan untuk kasus multi-kelas Anda harus menerapkan 1/(1+exp(-nilai)).

Itu tampaknya benar, melihat contoh. Terima kasih atas tipnya!

Tapi saya tidak mengerti mengapa ini didefinisikan secara berbeda untuk kasus biner dan multikelas.

Ya memang, sangat aneh dan membingungkan untuk sedikitnya.

Saya sampai pada kesimpulan itu dengan mengujinya pada UCI Car Dataset (di mana Anda mengklasifikasikan mobil menjadi tidak dapat diterima, dapat diterima, baik, atau sangat baik).

Akan menyenangkan untuk memiliki fitur tambahan di mana Anda dapat mengonversi pohon menjadi pohon sklearn atau sesuatu seperti itu (saat ini saya memiliki basis kode di mana saya dapat mengonversi pohon xgb dan pohon sklearn ini menjadi pohon keputusan yang saya tentukan sendiri). Dari yang terakhir, pohon sklearn, saya setidaknya 100% yakin bahwa ini dikonversi dengan benar. Untuk pohon xgb, keraguan tetap ada.

Setuju itu akan menjadi fitur yang hebat.

@GillesVandewiele @sosata : jadi dalam logistik itu (katakanlah untuk kelas i ) [ 1/(1+exp(-value)) ] , value adalah jumlah dari semua skor daun yang sesuai dengan asosiasi pohon kelas tertentu dengan sampel?

Sebagai contoh di komentar awal saya, jika saya mencoba memprediksi probabilitas kelas untuk [1 0 0]:

print(model.predict_proba(np.array([[1,0,0]])))

itu akan menghasilkan hasil ini:

[[ 0.41852772  0.29073614  0.29073614]]

Tidak ada cara untuk 1/(1+exp(-value)) untuk menghasilkan ini. Satu-satunya cara adalah bahwa probabilitas ini dihasilkan oleh fungsi _softmax_ dari nilai yang dijumlahkan di seluruh kelas:

p[i] = exp(val[i])/(exp(val[1])+exp(val[2])+...+exp(val[N]))

di mana i adalah kelas target (dari N kelas), dan val[i] adalah jumlah semua nilai yang dihasilkan dari pohon milik kelas itu.
Dalam contoh kami:

print(np.exp(+0.122449+0.10941)/(np.exp(+0.122449+0.10941)+np.exp(-0.0674157-0.0650523)+np.exp(-0.0674157-0.0650523)))
print(np.exp(-0.0674157-0.0650523)/(np.exp(+0.122449+0.10941)+np.exp(-0.0674157-0.0650523)+np.exp(-0.0674157-0.0650523)))
print(np.exp(-0.0674157-0.0650523)/(np.exp(+0.122449+0.10941)+np.exp(-0.0674157-0.0650523)+np.exp(-0.0674157-0.0650523)))

akan menghasilkan:

0.418527719063
0.290736140469
0.290736140469

yang persis seperti yang diberikan oleh fungsi predict_proba() kepada kita.

Sepertinya itu benar @sosata

Dalam kasus saya, saya ingin mengonversi setiap pohon secara individual (sehingga tidak menggunakan nilai daun dari pohon lain). Di sana, fungsi sigmoid tampaknya melakukan pekerjaan itu.

@sosata Apakah ada cara untuk mendapatkan fitur penting per kelas? Saya pikir implementasi saat ini hanya akan menjumlahkan semua kontribusi di semua kelas. R API tampaknya memiliki parameter untuk itu (lihat https://github.com/CodingCat/xgboost/commit/e9405236a01715be1550a7e3809f36fc69ad4e8a), meskipun.

@mlxai Saya juga hanya mencoba Python API dan saya tidak ingat saya bisa mendapatkannya per kelas.

Namun, menurut pendapat saya, definisi pentingnya fitur di XGBoost sebagai jumlah total pemisahan pada fitur itu agak sederhana. Secara pribadi, saya menghitung pentingnya fitur dengan menghapus fitur itu dari kumpulan fitur dan menghitung penurunan (atau peningkatan) akurasi yang dihasilkan, ketika model dilatih dan diuji tanpa fitur itu. Saya melakukan ini untuk semua fitur, dan saya mendefinisikan "fitur penting" sebagai jumlah penurunan (atau dikurangi peningkatan) dalam akurasi. Karena pelatihan dan pengujian dapat dilakukan beberapa kali dengan subset pelatihan/pengujian yang berbeda untuk setiap penghapusan fitur, seseorang juga dapat memperkirakan interval kepercayaan dari pentingnya fitur. Plus, dengan menghitung urgensi secara terpisah untuk setiap kelas, Anda mendapatkan urgensi fitur per kelas. Ini telah menghasilkan hasil yang lebih berarti dalam proyek saya daripada menghitung jumlah pemisahan.

@sosata Contoh Anda sangat intuitif, tetapi saya masih tidak tahu bagaimana Anda mendapatkan skor untuk setiap kelas, dapatkah Anda menjelaskannya sedikit? Terima kasih!

@chrisplyn Anda mengevaluasi semua pohon keputusan dalam ansambel Anda yang termasuk dalam kelas tertentu dan menjumlahkan skor yang dihasilkan.

Seperti yang @sosata nyatakan sebelumnya dengan benar: jumlah pohon dalam ansambel Anda akan sama dengan n_esimators * n_classes

@sosata contoh anda sangat jelas. Terima kasih banyak!

@chrisplyn Contoh prediksi [1,0,0] akan diklasifikasikan oleh booster[0~5], dan mendapatkan nilai daun masing-masing [0.122449, -0.0674157, -0.0674157, 0.10941, -0.0650523, -0.0650523].
Untuk booster[0, 3] milik class 0, booster[1, 4] milik class 1, dan booster[2, 5] milik class 2, jadi val[0] =(0.122449 + 0.10941), val[1 ] = (-0.0674157 + -0.0650523), val[2] = (-0.0674157 + -0.0650523).
Apakah lebih jelas?

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

nicoJiang picture nicoJiang  ·  4Komentar

ivannz picture ivannz  ·  3Komentar

frankzhangrui picture frankzhangrui  ·  3Komentar

trivialfis picture trivialfis  ·  3Komentar

tqchen picture tqchen  ·  4Komentar