Bagaimana cara mendapatkan parameter hiper dalam validasi lintas bersarang?


16

Saya telah membaca posting berikut untuk validasi silang bersarang dan masih belum 100% yakin apa yang harus saya lakukan dengan pemilihan model dengan validasi silang bersarang:

Untuk menjelaskan kebingungan saya, izinkan saya mencoba menjalani pemilihan model dengan metode validasi silang bersarang langkah demi langkah.

  1. Buat loop CV luar dengan K-Lipat. Ini akan digunakan untuk memperkirakan kinerja parameter-hiper yang "memenangkan" setiap loop CV dalam.
  2. Gunakan GridSearchCV untuk membuat loop CV bagian dalam di mana di setiap loop bagian dalam, GSCV menelusuri semua kombinasi ruang parameter yang mungkin dan menghasilkan set parameter terbaik.
  3. Setelah GSCV menemukan parameter terbaik di loop dalam, itu diuji dengan set tes di loop luar untuk mendapatkan estimasi kinerja.
  4. Lingkaran luar kemudian diperbarui ke flip berikutnya sebagai set tes dan sisanya sebagai set pelatihan, dan 1-3 diulang. Parameter total yang mungkin "menang" adalah jumlah lipatan yang ditunjuk di lingkaran luar. Jadi jika loop luar 5 kali lipat, maka Anda akan memiliki estimasi kinerja suatu algoritma dengan 5 set parameter hiper yang berbeda , BUKAN kinerja satu set parameter hiper tertentu.

Pendekatan ini diilustrasikan pada halaman contoh SKLearn: http://scikit-learn.org/stable/auto_examples/model_selection/plot_nested_cross_validation_iris.html

Pertanyaan: Setelah 4. , bagaimana Anda menentukan parameter hiper mana yang paling berhasil? Saya mengerti bahwa Anda ingin melatih algoritme Anda (mis. Regresi Logistik, Hutan Acak, dll.) Dengan kumpulan data COMPLETE di bagian akhir. Tetapi bagaimana Anda menentukan parameter hiper mana yang paling berhasil dalam validasi silang bersarang Anda? Pemahaman saya adalah bahwa untuk setiap loop dalam, satu set parameter hiper yang berbeda akan menang. Dan untuk loop luar, Anda mendapatkan estimasi kinerja GridSearchCV Anda, tetapi Anda tidak mendapatkan satu set parameter hiper tertentu. Jadi, dalam pembuatan model akhir, bagaimana Anda tahu parameter hiper apa yang digunakan? Itulah logika yang hilang yang saya kesulitan mengerti dari tapak lainnya.

Terima kasih sebelumnya atas tipnya, terutama jika @Dikran Marsupial dan @cbeleites dapat berbaur!

Sunting: Jika Anda bisa, harap dalam jawaban Anda gunakan istilah seperti "algoritma" dan "parameter hiper". Saya pikir salah satu sumber kebingungan bagi saya adalah ketika orang menggunakan istilah "model" atau "pemilihan model". Saya bingung apakah mereka berbicara tentang memilih algoritma yang digunakan atau parameter hiper apa yang digunakan.

Sunting 2: Saya telah membuat buku catatan yang menunjukkan dua cara melakukan validasi bersarang silang. Cara pertama adalah yang ditunjukkan pada contoh SKLearn, dan cara lain yang lebih panjang adalah yang saya tulis. Cara yang ditunjukkan di SKLearn tidak mengekspos hyperparameters "menang", tetapi cara saya yang lebih panjang. Namun pertanyaannya tetap sama. Setelah saya menyelesaikan validasi salib bersarang, bahkan dengan hiperparameter terbuka, apa yang harus saya lakukan sekarang? Seperti yang dapat Anda lihat dari hyperparameters di akhir notebook, mereka sedikit berbeda.


1
+1 untuk notebook. Pertanyaan ini juga menarik bagi saya dan akan ditindaklanjuti.
Arnold Klein

Jawaban:


6

(Saya yakin saya sudah menulis sebagian besar jawaban ini - tetapi tidak dapat menemukannya sekarang. Jika ada yang menemukan jawaban itu, harap tautkan). Saya melihat 2 pendekatan yang sedikit berbeda di sini, yang menurut saya masuk akal.

Tetapi pertama-tama beberapa terminologi:

  • Datang dari bidang terapan, model (pas / terlatih) bagi saya adalah model yang siap digunakan. Yaitu model berisi semua informasi yang diperlukan untuk menghasilkan prediksi untuk data baru. Dengan demikian, model tersebut juga mengandung hiperparameter . Seperti yang akan Anda lihat, sudut pandang ini terkait erat dengan pendekatan 2 di bawah ini.
  • OTOH, algoritme pelatihan menurut pengalaman saya tidak didefinisikan dengan baik dalam pengertian berikut: untuk mendapatkan model (pas), tidak hanya - sebut saja "fitting utama" - parameter model "normal" perlu dilakukan, tetapi juga hiperparameter perlu diperbaiki. Dari perspektif aplikasi saya, sebenarnya tidak ada banyak perbedaan antara parameter dan hyperparamers: keduanya merupakan bagian dari model , dan perlu diperkirakan / diputuskan selama pelatihan.
    Saya kira perbedaan di antara mereka terkait dengan perbedaan antara seseorang yang mengembangkan algoritma pelatihan baru yang biasanya menggambarkan kelas algoritma pelatihan bersama dengan beberapa parameter kemudi ( hyperparameters) yang sulit / tidak mungkin untuk diperbaiki (atau setidaknya untuk memperbaiki bagaimana mereka harus diputuskan / diperkirakan) tanpa pengetahuan aplikasi / domain.

Pendekatan 1: membutuhkan hasil optimasi yang stabil

Dengan pendekatan ini, "pelatihan model" adalah pemasangan parameter model "normal", dan diberikan hiperparameter . Validasi silang misalnya dalam menangani optimasi hiperparameter.

Langkah / asumsi penting di sini untuk menyelesaikan dilema yang set hiperparameternya harus dipilih adalah mengharuskan optimasi agar stabil . Validasi silang untuk tujuan validasi mengasumsikan bahwa semua model pengganti cukup mirip dengan model akhir (diperoleh dengan algoritma pelatihan yang sama yang diterapkan pada seluruh kumpulan data) untuk memungkinkan memperlakukannya sama (di antara mereka sendiri maupun dengan model akhir). Jika asumsi ini rusak dan

  1. model pengganti masih sama (atau setara) di antara mereka tetapi tidak dengan model akhir, kita berbicara tentang bias pesimistik yang terkenal dari validasi silang.

  2. Jika juga model pengganti tidak sama / setara satu sama lain, kami memiliki masalah dengan ketidakstabilan .

Untuk hasil optimasi loop dalam ini berarti bahwa jika optimasi stabil, tidak ada konflik dalam memilih hyperparameters . Dan jika variasi yang cukup diamati di seluruh hasil validasi silang dalam, optimasi tidak stabil . Situasi pelatihan yang tidak stabil memiliki masalah yang jauh lebih buruk daripada hanya keputusan yang mana dari hyperparameter yang akan dipilih, dan saya benar-benar merekomendasikan untuk mundur dalam kasus itu dan memulai proses pemodelan di seluruh

Ada pengecualian, di sini, meskipun: mungkin ada beberapa minimum lokal dalam optimasi menghasilkan kinerja yang sama untuk tujuan praktis. Membutuhkan juga pilihan di antara mereka untuk menjadi stabil mungkin merupakan persyaratan kuat yang tidak perlu - tetapi saya tidak tahu bagaimana keluar dari dilema ini.

Perhatikan bahwa jika tidak semua model menghasilkan set parameter kemenangan yang sama, Anda tidak boleh menggunakan perkiraan loop luar sebagai kesalahan generalisasi di sini:

  • hal
  • Tetapi kecuali jika tidak ada keputusan yang terlibat karena semua pemisahan menghasilkan parameter yang sama, ini akan merusak independensi di lingkaran luar: data uji masing-masing pemisahan sudah memasuki keputusan yang ditetapkan parameter yang menang karena melatih data di semua pemisahan lainnya dan dengan demikian digunakan untuk mengoptimalkan parameter.

Pendekatan 2: perlakukan penyetelan hyperparameter sebagai bagian dari pelatihan model

Pendekatan ini menjembatani perspektif "pengembang algoritma pelatihan" dan pengguna algoritma pelatihan yang diterapkan.

Pengembang algoritma pelatihan menyediakan algoritma pelatihan "telanjang" model = train_naked (trainingdata, hyperparameters). Sebagai kebutuhan pengguna yang diterapkan tunedmodel = train_tuned (trainingdata)yang juga mengurus perbaikan hyperparameters.

train_tuneddapat diimplementasikan misalnya dengan membungkus pengoptimal berbasis validasi silang di sekitar algoritma pelatihan telanjang train_naked.

train_tunedkemudian dapat digunakan seperti algoritma pelatihan lainnya yang tidak memerlukan input hyperparameter, misalnya outputnya tunedmodeldapat dikenai validasi silang. Sekarang hiperparameter diperiksa stabilitasnya seperti parameter "normal" harus diperiksa stabilitasnya sebagai bagian dari evaluasi validasi silang.

Ini sebenarnya yang Anda lakukan dan evaluasi dalam validasi silang bersarang jika Anda rata-rata kinerja semua model yang menang terlepas dari set parameter individu mereka.


Apa bedanya?

Kita mungkin berakhir dengan model akhir yang berbeda mengambil 2 pendekatan:

  • model akhir dalam pendekatan 1 akan train_naked (all data, hyperparameters from optimization)
  • sedangkan pendekatan 2 akan menggunakan train_tuned (all data)dan - saat menjalankan lagi optimasi hiperparameter pada kumpulan data yang lebih besar - ini mungkin berakhir dengan serangkaian hiperparameter yang berbeda.

Tetapi sekali lagi logika yang sama berlaku: jika kita menemukan bahwa model akhir memiliki parameter yang secara substansial berbeda dari model pengganti validasi silang, itu adalah gejala asumsi 1 yang dilanggar. Jadi IMHO, sekali lagi kita tidak memiliki konflik tetapi memeriksa apakah asumsi (implisit) kita dibenarkan. Dan jika tidak, kita tidak boleh bertaruh terlalu banyak untuk memiliki estimasi kinerja model akhir yang bagus.


Saya mendapat kesan (juga dari melihat sejumlah pertanyaan / kebingungan serupa di sini di CV) bahwa banyak orang berpikir tentang validasi silang bersarang melakukan pendekatan 1. Tetapi kesalahan generalisasi biasanya diperkirakan berdasarkan pendekatan 2, jadi itulah cara yang harus dilakukan untuk model akhir juga.


Contoh Iris

Ringkasan: Optimalisasi pada dasarnya tidak ada gunanya. Ukuran sampel yang tersedia tidak memungkinkan perbedaan antara kinerja setiap parameter yang ditetapkan di sini.

Dari sudut pandang aplikasi, kesimpulannya adalah bahwa tidak masalah mana dari 4 set parameter yang Anda pilih - yang bukan berita buruk: Anda menemukan dataran tinggi parameter yang relatif stabil. Inilah keuntungan dari validasi bersarang model tuned yang tepat: sementara Anda tidak dapat mengklaim bahwa itu adalah model yang optimal, Anda masih dapat mengklaim bahwa model yang dibangun pada seluruh data menggunakan pendekatan 2 akan memiliki akurasi sekitar 97% (interval kepercayaan 95% untuk 145 yang benar dari 150 kasus uji: 92 - 99%)

Perhatikan bahwa pendekatan 1 juga tidak sejauh yang terlihat - lihat di bawah ini: optimasi Anda secara tidak sengaja melewatkan "pemenang" yang relatif jelas karena ikatan (itu sebenarnya adalah gejala yang sangat jelas dari masalah ukuran sampel).

Walaupun saya tidak cukup dalam ke SVM untuk "melihat" bahwa C = 1 harus menjadi pilihan yang baik di sini, saya akan menggunakan kernel linear yang lebih ketat. Selain itu, saat Anda melakukan optimasi, tidak ada yang salah dengan memilih set parameter yang menang bahkan jika Anda sadar bahwa semua set parameter menghasilkan kinerja yang hampir sama.

Namun, di masa depan, pertimbangkan apakah pengalaman Anda menghasilkan perkiraan kasar tentang kinerja apa yang dapat Anda harapkan dan kira-kira model apa yang akan menjadi pilihan yang baik. Kemudian buat model itu (dengan hiperparameter yang diperbaiki secara manual) dan hitung interval kepercayaan untuk kinerjanya. Gunakan ini untuk memutuskan apakah mencoba mengoptimalkan itu masuk akal atau tidak. (Saya dapat menambahkan bahwa saya sebagian besar bekerja dengan data di mana mendapatkan 10 lebih banyak kasus independen tidak mudah - jika Anda berada di bidang dengan ukuran sampel independen yang besar, semuanya terlihat jauh lebih baik untuk Anda)

versi panjang:

Adapun contoh hasil pada irisset data. irismemiliki 150 kasing, SVM dengan kisi 2 x 2 parameter (2 kernel, 2 urutan besarnya untuk penalti C) dipertimbangkan.

Loop dalam memiliki perpecahan 129 (2x) dan 132 (6x) kasus. Set parameter "terbaik" tidak dapat diputuskan antara kernel linear atau rbf, keduanya dengan C = 1. Namun, keakuratan tes dalam semuanya (termasuk selalu kehilangan C = 10) dalam akurasi 94 - 98,5% yang diamati. Perbedaan terbesar yang kita miliki dalam salah satu pemisahan adalah 3 vs 8 kesalahan untuk rbf dengan C = 1 vs 10.

Tidak mungkin ini perbedaan yang signifikan. Saya tidak tahu bagaimana cara mengekstrak prediksi untuk masing-masing kasus di CV, tetapi bahkan dengan asumsi bahwa 3 kesalahan dibagi, dan model C = 10 membuat tambahan 5 kesalahan:

> table (rbf1, rbf10)
         rbf10
rbf1      correct wrong
  correct     124     5
  wrong         0     3

> mcnemar.exact(rbf1, rbf10)

    Exact McNemar test (with central confidence intervals)

data:  rbf1 and rbf10
b = 5, c = 0, p-value = 0.0625
alternative hypothesis: true odds ratio is not equal to 1

Ingatlah bahwa ada 6 perbandingan berpasangan di kisi 2 x 2, jadi kami harus mengoreksi beberapa perbandingan juga.


Pendekatan 1

Dalam 3 dari 4 pemisahan luar di mana rbf "menang" atas kernel linier, mereka sebenarnya memiliki akurasi estimasi yang sama (saya kira min dalam kasus ikatan mengembalikan indeks yang cocok pertama).

Mengubah kisi untuk params = {'kernel':['linear', 'rbf'],'C':[1,10]} menghasilkan

({'kernel': 'linear', 'C': 1}, 0.95238095238095233, 0.97674418604651159)
({'kernel': 'rbf', 'C': 1}, 0.95238095238095233, 0.98449612403100772)
({'kernel': 'linear', 'C': 1}, 1.0, 0.97727272727272729)
({'kernel': 'linear', 'C': 1}, 0.94444444444444442, 0.98484848484848486)
({'kernel': 'linear', 'C': 1}, 0.94444444444444442, 0.98484848484848486)
({'kernel': 'linear', 'C': 1}, 1.0, 0.98484848484848486)
({'kernel': 'linear', 'C': 1}, 1.0, 0.96212121212121215)

Pendekatan 2:

Di sini, clfadalah model terakhir Anda. Dengan random_state = 2, rbf dengan C = 1 menang:

In [310]: clf.grid_scores_
[...snip warning...]
Out[310]: 
[mean: 0.97333, std: 0.00897, params: {'kernel': 'linear', 'C': 1},
 mean: 0.98000, std: 0.02773, params: {'kernel': 'rbf', 'C': 1},
 mean: 0.96000, std: 0.03202, params: {'kernel': 'linear', 'C': 10},
 mean: 0.95333, std: 0.01791, params: {'kernel': 'rbf', 'C': 10}]

(terjadi sekitar 1 dalam 5 kali, 1 dalam 6 kali lineardan rbfdengan C = 1terikat pada peringkat 1)


4
Terima kasih @cbeleites! Saya membaca jawaban Anda di utas lain juga dan saya berharap Anda akan menjawab pertanyaan saya. Jawaban Anda sangat mendalam tetapi yang menjadi fokus pertanyaan saya adalah bagaimana menganalisis hasil CV bersarang; Saya masih sedikit tidak jelas tentang "apa yang harus dilakukan setelah saya bersarang CV". Dapatkah Anda melihat buku catatan yang saya buat (di dekat bagian akhir tulisan saya) dan dalam istilah awam menjelaskan apa yang harus dilakukan karena dua set hiperparameter yang berbeda telah diidentifikasi sebagai optimal dalam CV bersarang? Ini notebook yang sangat singkat, aku janji!
Pernapasan Berat

@HeavyBreathing Saya sudah membaca jawabannya, dan saya masih belum jelas tentang 'apa yang harus dilakukan setelah saya membuat CV bersarang'. Sudahkah Anda mengetahuinya dengan jelas?
stackunderflow

0

Saya sudah membaca pertanyaan Anda dan jawabannya di atas 2 kali (pertama kali 3 bulan lalu). Saya tertarik dan juga ingin menemukan cara mutlak yang tepat untuk melakukan validasi silang untuk data saya. Setelah banyak berpikir & membaca, sepertinya saya menemukan lubangnya dan inilah yang saya perbaiki:

Untuk menjelaskan kebingungan saya, izinkan saya mencoba menjalani pemilihan model dengan metode validasi silang bersarang langkah demi langkah.

  1. Buat loop CV luar dengan K-Lipat. Ini akan digunakan untuk memperkirakan kinerja parameter-hiper yang "memenangkan" setiap loop CV dalam.
  2. Gunakan GridSearchCV untuk membuat loop CV bagian dalam di mana di setiap loop bagian dalam, GSCV menelusuri semua kombinasi ruang parameter yang mungkin dan menghasilkan set parameter terbaik. (Catatan: di sini saya berasumsi: data untuk loop dalam = data pelatihan untuk loop luar. Anda mungkin bertanya: mengapa? Jawab: /programming/42228735/scikit-learn-gridsearchcv-with-multiple-repetitions/ 42230764 # 42230764 baca bagian jawaban oleh Vivek Kumar langkah 4)
  3. Setelah GSCV menemukan "parameter terbaik" di loop dalam (sebut saja itu pemenang dalam), diuji dengan set tes di loop luar untuk mendapatkan perkiraan kinerja (sebut saja outer_fold_score1).
  4. Loop luar kemudian diperbarui ke flip berikutnya sebagai set tes dan sisanya sebagai set pelatihan (untuk mengevaluasi "pemenang dalam" pada loop luar), "pemenang dalam" diuji lagi dengan set tes baru (outer_fold_score2). Kemudian lagi loop luar memperbarui ke flip berikutnya sampai loop selesai. Skor dari setiap lipatan (outer_fold_score 1,2 ..) akan menjadi rata-rata untuk mendapatkan skor "pemenang dalam" untuk loop luar (outer_score)
  5. Lingkaran luar kemudian diperbarui ke lipatan berikutnya sebagai set tes dan sisanya sebagai set pelatihan (untuk menemukan "pemenang dalam" berikutnya, dan 1-4 pengulangan (perhatikan bahwa ketika kita mengulangi 1 kita tidak membuat K- baru lipat tapi kami menggunakan Kfold luar yang sama setiap kali) .Dengan setiap siklus 1-4, kami mendapatkan "parameter terbaik" (atau "pemenang dalam") dengan outer_score. Yang dengan outer_score terbaik akan menjadi pemenang dari pemenang

Pemikiran:

  • Pada dasarnya pertanyaan Anda menyangkut bahwa ada banyak "parameter kemenangan" untuk loop luar. Masalahnya adalah Anda tidak menyelesaikan loop luar untuk mengevaluasi dan menemukan "pemenang luar". Langkah ke-4 Anda hanya mengevaluasi "pemenang dalam" dalam 1 lipatan di lingkaran luar, tetapi Anda tidak "mengulanginya". Karena itu saya perlu menggantinya dengan langkah ke-4 saya - "loop" langkah evaluasi di loop luar dan mendapatkan skor luar (dengan rata-rata)
  • Langkah ke-5 Anda memang melakukan pekerjaan "perulangan" di lingkaran luar, tetapi itu hanya untuk membangun "pemenang dalam" lainnya. Itu tidak mengulangi "evaluasi" dari "pemenang dalam" ini di lingkaran luar

Apakah ini benar-benar menjawab pertanyaan?
Michael R. Chernick

0

Anda tidak menggunakan validasi silang bersarang untuk memilih parameter hiper algoritma, metode ini digunakan untuk memperkirakan kesalahan generalisasi prosedur pembuatan model Anda . Di mana dengan prosedur pembuatan model, saya bermaksud semua langkah yang Anda terapkan untuk mencapai model akhir yang akan Anda gunakan di lapangan.
Prosedur pembuatan model dapat disusun oleh aturan yang Anda terapkan untuk memutuskan preprocessing mana yang akan diterapkan pada data, fitur mana yang digunakan dan akhirnya parameter hiper mana yang digunakan. Anggap ini sebagai semacam "meta-algoritma" yang menerima sebagai input dataset tertentuDdan menghasilkan output sebuah "algoritma", terdiri dari tetap set preprocessing transformasi, fitur dan nilai-nilai parameter akhirnya hiper.

Misalnya katakanlah Anda punya X,ysebagai matriks desain dan target dan Anda ingin melatih classifier:
1. yang hanya menggunakan yang pertamax fitur dalam X yang memiliki korelasi tertinggi dengan y.
2. Anda memilih nilai parameter hiper dengan meminimalkan estimasi kesalahan validasi silang 10 kali lipat.

Jika Anda menerapkan dua langkah ini untuk pasangan tertentu X,y Anda akan mendapatkan algoritme tertentu dengan serangkaian fitur dan parameter hiper tetap yang tidak harus sama dengan yang Anda dapatkan untuk X,ymeskipun prosedur pembangunan model akan sama yaitu: langkah 1 + 2 yang tidak terikat pada dataset tertentu.
Katakanlah Anda melakukan semua hal di atas tanpa membagi data Anda menjadi tes kereta karena Anda memiliki dataset kecil, bagaimana Anda memperkirakan kesalahan generalisasi dari classifier yang baru saja Anda buat? Bisakah Anda menggunakan kesalahan terbaik yang Anda temukan di validasi silang pada langkah 2?
Tidak , masalah besar pertama adalah pada langkah 1 di mana Anda menggunakan semua data untuk memilih fitur yang akan digunakan. Oleh karena itu, bahkan ketika Anda melakukan validasi silang pada langkah 2, fitur-fiturnya sudah akan melihat dan mengingat beberapa informasi yang ada dalam flip tes di setiap menjalankan validasi silang. Hasilnya akan menjadi perkiraan yang terlalu optimis dari kesalahan pengujian dan ini disebutbias pemilihan fitur . Untuk memperhitungkan ini dalam estimasi Anda, Anda perlu memasukkan langkah pemilihan fitur di dalam loop validasi silang dari langkah 2.
Oke, sekarang apakah kita baik-baik saja? Apakah kesalahan terbaik ditemukan dalam validasi silang dengan langkah pemilihan fitur di dalam loop adalah perkiraan yang adil dari kesalahan generalisasi?
Secara teori jawabannya masih tidak , masalahnya adalah bahwa parameter hiper Anda telah dipilih untuk meminimalkan kesalahan validasi silang pada dataset tertentu yang Anda inginkan, sehingga dalam arti tertentu Anda menyesuaikan parameter hiper ke data Anda dengan risiko over-fitting mereka, dan ini disebut bias pemilihan model. Tetapi apakah ini merupakan keprihatinan dalam praktik? Itu tergantung oleh aplikasi spesifik: itu mungkin menjadi lebih parah, seperti overfitting dalam pelatihan, ketika dataset kecil dan jumlah parameter hiper yang akan disetel relatif besar. Untuk memperhitungkan ini ketika memperkirakan kesalahan generalisasi Anda akan menerapkan validasi silang bersarang seperti yang Anda jelaskan, yang kemudian akan memberi Anda estimasi yang benar dari kesalahan generalisasi Anda.
Akhirnya untuk menjawab pertanyaan terakhir Anda, setelah memiliki perkiraan yang adil dari kesalahan generalisasi “model building procedure” Anda dengan validasi silang bersarang, Anda cukup menerapkan prosedur (langkah 1 + 2) untuk seluruh dataset Anda mendapatkan model dengan set tetap. fitur dan menetapkan nilai parameter hiper, tetapi perlu diingat bahwa kesalahan yang kami harapkan model ini miliki pada data yang tidak terlihat adalah estimasi validasi silang bersarang .

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.