Seleksi pentingnya fitur untuk k-tetangga terdekat (well, atau hiperparameter lainnya) berdasarkan keturunan yang mirip dengan gradien

Omong kosong yang sejati tidak hanya dapat memenuhi hal yang mustahil, tetapi juga berfungsi sebagai contoh peringatan

Bereksperimen dengan tugas paling sederhana dalam pembelajaran mesin, saya menemukan bahwa akan menarik untuk memilih 18 hiperparameter pada saat yang sama dalam rentang yang cukup luas. Dalam kasus saya, semuanya sangat sederhana sehingga tugas dapat diambil dengan kekuatan komputer yang kasar.

Saat mempelajari sesuatu, akan sangat menarik untuk menemukan beberapa jenis sepeda. Terkadang ternyata benar-benar muncul dengan sesuatu yang baru. Kadang-kadang ternyata semuanya ditemukan sebelum saya. Tetapi bahkan jika saya hanya mengulangi jalan yang ditempuh jauh sebelum saya, sebagai hadiah saya sering mendapatkan pemahaman tentang mekanisme yang mendasari algoritma kemampuan mereka dan keterbatasan internal. Saya mengundang Anda untuk itu.

Dalam Python dan DS, secara sederhana, saya adalah seorang pemula, dan saya melakukan banyak hal yang dapat diimplementasikan ke dalam satu tim sesuai dengan kebiasaan pemrograman lama saya, yang Python menghukum dengan memperlambat, bukan pada waktu, tetapi atas perintah besarnya. Karena itu, saya mengunggah semua kode saya ke repositori. Jika Anda tahu cara menerapkannya dengan lebih efisien - jangan malu-malu, edit di sana, atau tulis di komentar. https://github.com/kraidiky/GDforHyperparameters

Mereka yang sudah menjadi ahli data yang keren dan telah mencoba segalanya dalam hidup ini akan menarik, saya percaya, visualisasi dari proses pembelajaran, yang berlaku tidak hanya untuk tugas ini.

Perumusan masalah


Ada kursus DS yang bagus dari ODS.ai dan ada Klasifikasi kuliah ketiga , pohon keputusan dan metode tetangga terdekat . Di sana, ditunjukkan pada data sintetis yang sangat sederhana dan mungkin bagaimana pohon keputusan paling sederhana memberikan akurasi 94,5%, dan metode yang sangat sederhana yang sama dari k tetangga terdekat memberikan 89% tanpa preprocessing

Impor dan muat data
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
%matplotlib inline
import warnings
warnings.filterwarnings('ignore')

df = pd.read_csv('data/telecom_churn.csv')
df['Voice mail plan'] = pd.factorize(df['Voice mail plan'])[0]
df['International plan'] = pd.factorize(df['International plan'])[0]
df['Churn'] = df['Churn'].astype('int32')
states = df['State']
y = df['Churn']
df.drop(['State','Churn'], axis = 1, inplace=True)
df.head()

Bandingkan kayu dengan knn
%%time
from sklearn.model_selection import train_test_split, StratifiedKFold
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV, cross_val_score
from sklearn.metrics import accuracy_score

X_train, X_holdout, y_train, y_holdout = train_test_split(df.values, y, test_size=0.3,
random_state=17)

tree = DecisionTreeClassifier(random_state=17, max_depth=5)
knn = KNeighborsClassifier(n_neighbors=10)

tree_params = {'max_depth': range(1,11), 'max_features': range(4,19)}
tree_grid = GridSearchCV(tree, tree_params, cv=10, n_jobs=-1, verbose=False)
tree_grid.fit(X_train, y_train)
tree_grid.best_params_, tree_grid.best_score_, accuracy_score(y_holdout, tree_grid.predict(X_holdout))

({'max_depth': 6, 'max_features': 16}, 0.944706386626661, 0.945)

sama untuk knn
%%time
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

knn_pipe = Pipeline([('scaler', StandardScaler()), ('knn', KNeighborsClassifier(n_jobs=-1))])
knn_params = {'knn__n_neighbors': range(1, 10)}
knn_grid = GridSearchCV(knn_pipe, knn_params, cv=10, n_jobs=-1, verbose=False)

knn_grid.fit(X_train, y_train)
knn_grid.best_params_, knn_grid.best_score_, accuracy_score(y_holdout, knn_grid.predict(X_holdout))

({'knn__n_neighbors': 9}, 0.8868409772824689, 0.891)
Pada titik ini, saya merasa kasihan pada knn yang jelas-jelas tidak jujur, karena kami tidak bekerja dengan metrik. Saya tidak berpikir dengan otak saya, saya mengambil feature_importances_ dari pohon dan menormalkan inputnya. Dengan demikian, semakin penting fitur, semakin besar kontribusinya adalah jarak antar titik.

Kami memberi makan data yang dinormalisasi dengan pentingnya fitur
%%time
feature_importances = pd.DataFrame({'features': df.columns, 'importance':tree_grid.best_estimator_.feature_importances_})
print(feature_importances.sort_values(by=['importance'], inplace=False, ascending=False))

scaler = StandardScaler().fit(X_train)
X_train_transformed = scaler.transform(X_train)
X_train_transformed = X_train_transformed * np.array(feature_importances['importance'])

X_holdout_transformed = scaler.transform(X_holdout)
X_holdout_transformed = X_holdout_transformed * np.array(feature_importances['importance'])

knn_grid = GridSearchCV(KNeighborsClassifier(n_jobs=-1), {'n_neighbors': range(1, 11, 2)}, cv=5, n_jobs=-1, verbose=False)
knn_grid.fit(X_train_transformed, y_train)
print (knn_grid.best_params_, knn_grid.best_score_, accuracy_score(y_holdout, knn_grid.predict(X_holdout_transformed)))

5Total menit hari0,270386
17Panggilan layanan pelanggan0,147185
8Total setiap menit0,135475
2Rencana internasional0,097249
enambelasTotal biaya intl0,091671
lima belasTotal panggilan intl09.090008
4Pesan nomor vmail0,050646
10Total biaya malam0,038593
7Total biaya hari0,026422
3Paket pesan suara0,017068
sebelasTotal menit malam0,014185
tigabelasTotal biaya malam0,005742
12Total panggilan malam0,005502
9Total panggilan malam0,003614
6Panggilan total hari0,002246
14Total intl menit0.002009
0Account length0.001998
1Area code0.000000

{'n_neighbors': 5} 0.909129875696528 0.913

Pohon hanya membagikan sedikit pengetahuannya dengan knn dan sekarang kita melihat 91%. Itu tidak jauh dari 94,5% pohon vanila. Dan kemudian sebuah ide muncul padaku. Tapi bagaimana, sebenarnya, kita perlu menormalkan input agar knn menunjukkan hasil terbaik?

Pertama, kami akan memperkirakan dalam pikiran kami berapa banyak ini sekarang akan dianggap "dahi". 18 parameter, untuk masing-masing kita membuat, katakanlah, 10 langkah yang mungkin dari faktor-faktor dalam skala logaritmik. Kami mendapatkan 10e18 opsi. Salah satu opsi dengan semua kemungkinan jumlah tetangga yang aneh adalah kurang dari 10 dan validasi silang juga 10, saya pikir sekitar 1,5 detik. Ternyata 42 miliar tahun. Mungkin gagasan meninggalkan perhitungan untuk malam hari harus ditinggalkan. :) Dan di suatu tempat di sekitar sini saya berpikir, “Hei! Jadi saya akan membuat sepeda yang akan terbang! "

Pencarian Gradien


Bahkan, tugas ini kemungkinan besar hanya memiliki satu maksimum yang tersedia. Ya, itu bukan satu-satunya tentu saja, seluruh area hasil yang baik, tetapi mereka hampir sama. Oleh karena itu, kita bisa berjalan di sepanjang gradien dan menemukan titik yang paling cocok. Pikiran pertama adalah menggeneralisasi algoritma genetika, tetapi di sini medan adaptif tampaknya tidak terlalu menyilang, dan ini akan sedikit berlebihan.

Saya akan mencoba melakukannya secara manual sebagai permulaan. Untuk mendorong faktor sebagai hiperparameter, saya harus berurusan dengan scaler. Dalam contoh sebelumnya, seperti dalam pelajaran, saya menggunakan StandartScaler, yang memusatkan sampel pelatihan rata-rata dan membuat sigma = 1. Untuk skala itu baik di dalam pipa, hyperparameter harus dibuat sedikit lebih rumit. Saya mulai mencari di antara konverter yang berbaring di sklearn. Memproses untuk sesuatu yang cocok untuk kasus saya, tetapi tidak menemukan apa pun. Oleh karena itu, saya mencoba mewarisi dari StandartScaler dengan menggantung seikat faktor tambahan di atasnya.

Kelas untuk nominalisasi dan kemudian perkalian dengan skala yang sedikit kompatibel dengan pipa sklearn
from sklearn.base import TransformerMixin
class StandardAndPoorScaler(StandardScaler, TransformerMixin):
    #normalization = None
    def __init__(self, copy=True, with_mean=True, with_std=True, normalization = None):
        #print("new StandardAndPoorScaler(normalization=", normalization.shape if normalization is not None else normalization, ") // ", type(self))
        self.normalization = normalization
        super().__init__(copy, with_mean, with_std)
    def fit(self, X, y=None):
        #print(type(self),".fit(",X.shape, ",", y.shape if y is not None else "<null>",")")
        super().fit(X, y)
        return self
    def partial_fit(self, X, y=None):
        #print(type(self),".partial_fit(",X.shape, ",", y.shape if y is not None else "<null>)")
        super().partial_fit(X, y)
        if self.normalization is None:
            self.normalization = np.ones((X.shape[1]))
        elif type(self.normalization) != np.ndarray:
            self.normalization = np.array(self.normalization)
        if X.shape[1] != self.normalization.shape[0]:
            raise "X.shape[1]="+X.shape[1]+" in equal self.scale.shape[0]="+self.normalization.shape[0]
    def transform(self, X, copy=None):
        #print(type(self),".transform(",X.shape,",",copy,").self.normalization", self.normalization)
        Xresult = super().transform(X, copy)
        Xresult *= self.normalization
        return Xresult
    def _reset(self):
        #print(type(self),"._reset()")
        super()._reset()
    
scaler = StandardAndPoorScaler(normalization = feature_importances['importance'])
scaler.fit(X = X_train, y = None)
print(scaler.normalization)

Mencoba menerapkan kelas ini
%%time
knn_pipe = Pipeline([('scaler', StandardAndPoorScaler()), ('knn', KNeighborsClassifier(n_jobs=-1))])

knn_params = {'knn__n_neighbors': range(1, 11, 4), 'scaler__normalization': [feature_importances['importance']]}
knn_grid = GridSearchCV(knn_pipe, knn_params, cv=5, n_jobs=-1, verbose=False)

knn_grid.fit(X_train, y_train)
knn_grid.best_params_, knn_grid.best_score_, accuracy_score(y_holdout, knn_grid.predict(X_holdout))

({'knn__n_neighbors': 5, 'scaler__normalalization': Nama: importance, dtype: float64}, 0.909558508358337, 0.913)

Hasilnya sedikit berbeda dari harapan saya. Ya, pada prinsipnya, semuanya bekerja. Hanya untuk memahami ini, saya harus mereproduksi kelas ini dengan semua isi dari awal dalam tiga jam, dan baru kemudian saya menyadari bahwa cetak tidak mencetak bukan karena sklearn dibuat dengan cara yang salah, tetapi karena GridSearchCV membuat klon di aliran utama , tetapi mengkonfigurasi dan melatihnya di utas lainnya. Dan semua yang Anda cetak di aliran lain menghilang hingga terlupakan. Tetapi jika Anda menempatkan n_jobs = 1, maka semua panggilan ke fungsi yang diganti ditampilkan sebagai imut. Pengetahuan keluar sangat mahal, sekarang Anda juga memilikinya, dan Anda membayarnya dengan membaca artikel yang membosankan.

Oke, mari kita lanjutkan. Sekarang saya ingin memberikan varians untuk masing-masing parameter mereka, dan kemudian memberikannya sedikit kurang di sekitar nilai terbaik, dan seterusnya sampai saya mendapatkan hasil yang mirip dengan kenyataan. Ini akan menjadi garis dasar kasar pertama tentang apa yang akhirnya harus mendapatkan algoritma dari mimpi saya.

Saya akan membentuk beberapa opsi untuk reweighting, berbeda dalam beberapa parameter
feature_base = feature_importances['importance']
searchArea = np.array([feature_base - .05, feature_base, feature_base + .05])
searchArea[searchArea < 0] = 0
searchArea[searchArea > 1] = 1
print(searchArea[2,:] - searchArea[0,:])

import itertools

affected_props = [2,3,4]
parametrs_ranges = np.concatenate([
    np.linspace(searchArea[0,affected_props], searchArea[1,affected_props], 2, endpoint=False),
    np.linspace(searchArea[1,affected_props], searchArea[2,affected_props], 3, endpoint=True)]).transpose()

print(parametrs_ranges) #      .  125 
recombinations = itertools.product(parametrs_ranges[0],parametrs_ranges[1],parametrs_ranges[1])

variances = []
for item in recombinations: #          ,       Python .
    varince = feature_base.copy()
    varince[affected_props] = item
    variances.append(varince)
print(variances[0])
print(len(variances))
#  knn   ,               .

Nah, kumpulan data untuk percobaan pertama sudah siap. Sekarang saya akan mencoba bereksperimen dengan data, untuk memulai dengan pencarian lengkap dari 15 opsi yang dihasilkan.

Kami membuat pemilihan parameter percobaan seperti dalam artikel
%%time
#scale = np.ones([18])
knn_pipe = Pipeline([('scaler', StandardAndPoorScaler()), ('knn', KNeighborsClassifier(n_neighbors = 7 , n_jobs=-1))])

knn_params = {'scaler__normalization': variances} # 'knn__n_neighbors': range(3, 9, 2), 
knn_grid = GridSearchCV(knn_pipe, knn_params, cv=10, n_jobs=-1, verbose=False)

knn_grid.fit(X_train, y_train)
knn_grid.best_params_, knn_grid.best_score_, accuracy_score(y_holdout, knn_grid.predict(X_holdout))

Nah, semuanya buruk, waktu dihabiskan untuk terobosan, dan hasilnya sangat tidak stabil. Ini juga terlihat dari pemeriksaan X_holdout, hasilnya menari seperti dalam kaleidoskop dengan sedikit perubahan pada data input. Saya akan mencoba pendekatan yang berbeda. Saya hanya akan mengubah satu parameter pada satu waktu, tetapi dengan diskritisasi yang jauh lebih besar.

Saya mengubah satu properti ke-4
%%time
affected_property = 4
parametrs_range = np.concatenate([
    np.linspace(searchArea[0,affected_property], searchArea[1,affected_property], 29, endpoint=False),
    np.linspace(searchArea[1,affected_property], searchArea[2,affected_property], 30, endpoint=True)]).transpose()

print(searchArea[1,affected_property])
print(parametrs_range) # C   ,  .


variances = []
for item in parametrs_range: #          ,       Python .
    varince = feature_base.copy()
    varince[affected_property] = item
    variances.append(varince)
print(variances[0])
print(len(variances))
#  knn   ,               .

knn_pipe = Pipeline([('scaler', StandardAndPoorScaler()), ('knn', KNeighborsClassifier(n_neighbors = 7 , n_jobs=-1))])

knn_params = {'scaler__normalization': variances} # 'knn__n_neighbors': range(3, 9, 2), 
knn_grid = GridSearchCV(knn_pipe, knn_params, cv=10, n_jobs=-1, verbose=False)

knn_grid.fit(X_train, y_train)
knn_grid.best_params_, knn_grid.best_score_, accuracy_score(y_holdout, knn_grid.predict(X_holdout))

({'scaler__normalisasi': 4 0,079957 Nama: importance, dtype: float64}, 0,9099871410201458, 0,913)

Nah, apa yang kita miliki dengan angsa? Pergeseran satu hingga dua persepuluh persen pada validasi silang, dan setengah persen lompatan pada X_holdout jika Anda melihat berbagai properti yang terpengaruh. Tampaknya penting dan murah untuk memperbaiki situasi jika Anda mulai dengan fakta bahwa pohon memberi kita tidak mungkin pada data tersebut. Tetapi anggaplah kita tidak memiliki distribusi berat awal yang diketahui, dan mencoba melakukan hal yang sama pada titik acak dalam siklus dengan langkah-langkah kecil. Sangat menarik apa yang akan kita temui.

Pengisian awal
searchArea = np.array([np.zeros((18,)), np.ones((18,)) /18, np.ones((18,))])
print(searchArea[:,0])

history_parametrs = [searchArea[1,:].copy()]
scaler = StandardAndPoorScaler(normalization=searchArea[1,:])
scaler.fit(X_train)
knn = KNeighborsClassifier(n_neighbors = 7 , n_jobs=-1)
knn.fit(scaler.transform(X_train), y_train)
history_holdout_score = [accuracy_score(y_holdout, knn.predict(scaler.transform(X_holdout)))]

Berfungsi sedikit mengubah satu parameter (dengan log debug)
%%time
def changePropertyNormalization(affected_property, points_count = 15):
    test_range = np.concatenate([
        np.linspace(searchArea[0,affected_property], searchArea[1,affected_property], points_count//2, endpoint=False),
        np.linspace(searchArea[1,affected_property], searchArea[2,affected_property], points_count//2 + 1, endpoint=True)]).transpose()
    variances = [searchArea[1,:].copy() for i in range(test_range.shape[0])]
    for row in range(len(variances)):
        variances[row][affected_property] = test_range[row]
    
    knn_pipe = Pipeline([('scaler', StandardAndPoorScaler()), ('knn', KNeighborsClassifier(n_neighbors = 7 , n_jobs=-1))])
    knn_params = {'scaler__normalization': variances} # 'knn__n_neighbors': range(3, 9, 2), 
    knn_grid = GridSearchCV(knn_pipe, knn_params, cv=10, n_jobs=-1, verbose=False)

    knn_grid.fit(X_train, y_train)
    holdout_score = accuracy_score(y_holdout, knn_grid.predict(X_holdout))
    best_param = knn_grid.best_params_['scaler__normalization'][affected_property]
    print(affected_property,
          'property:', searchArea[1, affected_property], "=>", best_param,
          'holdout:', history_holdout_score[-1], "=>", holdout_score, '(', knn_grid.best_score_, ')')
    #             .
    before = searchArea[:, affected_property]
    propertySearchArea = searchArea[:, affected_property].copy()
    if best_param == propertySearchArea[0]:
        print('|<<')
        searchArea[0, affected_property] = best_param/2 if best_param > 0.01 else 0
        searchArea[2, affected_property] = (best_param + searchArea[2, affected_property])/2
        searchArea[1, affected_property] = best_param
    elif best_param == propertySearchArea[2]:
        print('>>|')
        searchArea[2, affected_property] = (best_param + 1)/2 if best_param < 0.99 else 1
        searchArea[0, affected_property] = (best_param + searchArea[0, affected_property])/2
        searchArea[1, affected_property] = best_param
    elif best_param < (propertySearchArea[0] + propertySearchArea[1])/2:
        print('<<')
        searchArea[0, affected_property] = max(propertySearchArea[0]*1.1 - .1*propertySearchArea[1], 0)
        searchArea[2, affected_property] = (best_param + propertySearchArea[2])/2
        searchArea[1, affected_property] = best_param
    elif best_param > (propertySearchArea[1] + propertySearchArea[2])/2:
        print('>>')
        searchArea[0, affected_property] = (best_param + propertySearchArea[0])/2
        searchArea[2, affected_property] = min(propertySearchArea[2]*1.1 - .1*propertySearchArea[1], 1)
        searchArea[1, affected_property] = best_param
    elif best_param < propertySearchArea[1]:
        print('<')
        searchArea[2, affected_property] = searchArea[1, affected_property]*.25 + .75*searchArea[2, affected_property]
        searchArea[1, affected_property] = best_param
    elif best_param > propertySearchArea[1]:
        print('>')
        searchArea[0, affected_property] = searchArea[1, affected_property]*.25 + .75*searchArea[0, affected_property]
        searchArea[1, affected_property] = best_param
    else:
        print('=')
        searchArea[0, affected_property] = searchArea[1, affected_property]*.25 + .75*searchArea[0, affected_property]
        searchArea[2, affected_property] = searchArea[1, affected_property]*.25 + .75*searchArea[2, affected_property]
    normalization = searchArea[1,:].sum() #,      .
    searchArea[:,:] /= normalization
    print(before, "=>",searchArea[:, affected_property])
    history_parametrs.append(searchArea[1,:].copy())
    history_holdout_score.append(holdout_score)
    
changePropertyNormalization(1, 9)
changePropertyNormalization(1, 9)

Saya tidak mengoptimalkan apa pun di mana pun, dan sebagai hasilnya, saya mengambil langkah tegas berikutnya selama hampir setengah jam:

Teks tersembunyi
40 .
%%time
#   
searchArea = np.array([np.zeros((18,)), np.ones((18,)) /18, np.ones((18,))])
print(searchArea[:,0])

history_parametrs = [searchArea[1,:].copy()]
scaler = StandardAndPoorScaler(normalization=searchArea[1,:])
scaler.fit(X_train)
knn = KNeighborsClassifier(n_neighbors = 7 , n_jobs=-1)
knn.fit(scaler.transform(X_train), y_train)
history_holdout_score = [accuracy_score(y_holdout, knn.predict(scaler.transform(X_holdout)))]

for tick in range(40):
    for p in range(searchArea.shape[1]):
        changePropertyNormalization(p, 7)
    
print(searchArea[1,:])
print(history_holdout_score)

Akurasi yang dihasilkan dari knn: 91,9% Lebih baik daripada ketika kita merobek data dari pohon. Dan jauh, jauh lebih baik daripada di versi aslinya. Bandingkan apa yang kami miliki dengan pentingnya fitur sesuai dengan pohon keputusan:

Visualisasi tentang pentingnya fitur sesuai dengan knn
feature_importances['knn_importance'] = history_parametrs[-1]
diagramma = feature_importances.copy()
indexes = diagramma.index
diagramma.index = diagramma['features']
diagramma.drop('features', 1, inplace = True)
diagramma.plot(kind='bar');
plt.savefig("images/pic1.png", format = 'png')
plt.show()
feature_importances





Tampaknya? Ya, sepertinya. Namun jauh dari identik. Pengamatan menarik. Ada beberapa fitur dalam kumpulan data yang saling menduplikasi satu sama lain, misalnya, 'Total malam menit' dan 'Total biaya malam hari'. Jadi perhatikan, knn sendiri menggerogoti sebagian besar fitur berulang tersebut.

Kami akan menyimpan hasilnya ke file, jika tidak, agak tidak nyaman untuk kembali bekerja ....
parametrs_df = pd.DataFrame(history_parametrs)
parametrs_df['scores'] = history_holdout_score
parametrs_df.index.name = 'index'
parametrs_df.to_csv('parametrs_and_scores.csv')

temuan


Nah, Hasil .919 itu sendiri tidak buruk untuk knn, ada satu setengah kali lebih sedikit kesalahan daripada di versi vanilla dan 7% lebih sedikit daripada ketika kita mengambil pohon feature_importance untuk dikendarai. Tetapi hal yang paling menarik adalah bahwa sekarang kita memiliki feature_importance sesuai dengan knn itu sendiri. Ini agak berbeda dari apa yang dikatakan pohon itu kepada kita. Sebagai contoh, tree dan knn memiliki pendapat berbeda tentang tanda mana yang tidak penting bagi kita sama sekali.

Yah, pada akhirnya. Kami mendapat sesuatu yang relatif baru dan tidak biasa, memiliki cadangan pengetahuan tiga mlcourse kuliah.saya dapat

dan Google untuk menjawab pertanyaan sederhana tentang python. Menurut saya, tidak buruk.

Sekarang slide


Produk sampingan dari pekerjaan algoritma adalah jalur yang telah dilalui. Benar, jalannya adalah 18 dimensi, yang sedikit menghalangi kesadarannya, untuk mengikuti secara real time apa yang dilakukan algoritma di sana, belajar atau menggunakan sampah tidak begitu nyaman. Menurut jadwal kesalahan, ini, pada kenyataannya, tidak selalu terlihat. Kesalahan mungkin tidak berubah secara nyata untuk waktu yang lama, tetapi algoritma ini sangat sibuk, merangkak di sepanjang lembah sempit panjang di ruang adaptif. Oleh karena itu, saya akan menerapkan, sebagai permulaan, pendekatan pertama yang paling sederhana namun cukup informatif - Saya memproyeksikan ruang 18 dimensi secara acak ke ruang dua dimensi sehingga kontribusi semua parameter, terlepas dari signifikansinya, adalah tunggal. Faktanya, jalur 18 dimensi sangat kecil, dalam artikel kami Peeping Over the Throws of Neural Network Saya juga mengagumi ruang skala semua sinapsis yang dimiliki jaringan saraf dan itu bagus dan informatif.

Saya membaca data dari file, jika saya kembali bekerja, setelah melewati tahap pelatihan itu sendiri
parametrs_df = pd.read_csv('parametrs_and_scores.csv', index_col = 'index')
history_holdout_score = np.array(parametrs_df['scores'])
parametrs_df.drop('scores',axis=1)
history_parametrs = np.array(parametrs_df.drop('scores',axis=1))

Kesalahan validasi berhenti berubah dari beberapa titik. Di sini mungkin untuk menghentikan pembelajaran otomatis dan menggunakan fungsi yang diterima selama sisa hidup saya, tetapi saya sudah punya sedikit waktu. :(

Kami menentukan berapa banyak belajar.
last = history_holdout_score[-1]
steps = np.arange(0, history_holdout_score.shape[0])[history_holdout_score != last].max()
print(steps/18)

35.5555555555555556
Kami mengubah satu parameter pada satu waktu, sehingga satu siklus optimasi terdiri dari 18 langkah. Ternyata kami memiliki 36 langkah yang berarti, atau sesuatu seperti itu. Sekarang mari kita coba memvisualisasikan lintasan di mana metode ini dilatih.


Teks tersembunyi
%%time
#    :
import matplotlib.pyplot as plt
%matplotlib inline
import random
import math
random.seed(17)
property_projection = np.array([[math.sin(a), math.cos(a)] for a in [random.uniform(-math.pi, math.pi) for i in range(history_parametrs[0].shape[0])]]).transpose()
history = np.array(history_parametrs[::18]) #   - 18 .
#           . :(
points = np.array([(history[i] * property_projection).sum(axis=1) for i in range(history.shape[0])])
plt.plot(points[:36,0],points[0:36,1]);
plt.savefig("images/pic2.png", format = 'png')
plt.show()



Dapat dilihat bahwa bagian penting dari perjalanan diselesaikan dalam empat langkah pertama. Mari kita lihat sisanya dengan peningkatan

Tanpa 4 poin pertama
plt.plot(points[4:36,0],points[4:36,1]);
plt.savefig("images/pic3.png", format = 'png')



Mari kita melihat lebih dekat pada bagian akhir jalan dan melihat apa yang dilakukan guru setelah mencapai tujuannya.

semakin dekat
plt.plot(points[14:36,0],points[14:36,1]);
plt.savefig("images/pic4.png", format = 'png')
plt.show()
plt.plot(points[24:36,0],points[24:36,1]);
plt.plot(points[35:,0],points[35:,1], color = 'red');
plt.savefig("images/pic5.png", format = 'png')
plt.show()





Dapat dilihat bahwa algoritma sedang dilatih dengan seksama. Sampai dia menemukan tujuannya. Titik spesifik, tentu saja, tergantung pada pengacakan dalam cross-validation. Tetapi terlepas dari titik tertentu, gambaran umum tentang apa yang terjadi dapat dimengerti.

Ngomong-ngomong, saya menggunakan jadwal seperti itu untuk menunjukkan proses belajar.
Tidak seluruh lintasan ditampilkan, tetapi beberapa langkah terakhir dengan perataan geser skala. Contohnya dapat ditemukan di artikel saya yang lain, “Kami Memata-matai Jaringan Neural”. Dan ya, tentu saja, semua orang yang mengalami visualisasi seperti itu segera bertanya mengapa semua faktor memiliki bobot, kepentingan, dan perbedaan yang sama. Terakhir kali dalam artikel, saya mencoba menimbang kembali pentingnya sinapsis dan ternyata kurang informatif.

Kali ini, dipersenjatai dengan pengetahuan baru, saya akan mencoba menggunakan t-SNE untuk menyebarkan ruang multidimensi ke dalam proyeksi di mana semuanya bisa lebih baik.

t-SNE
%%time
import sklearn.manifold as manifold
tsne = manifold.TSNE(random_state=19)
tsne_representation = tsne.fit_transform(history)
plt.plot(tsne_representation[:, 0], tsne_representation[:, 1])
plt.savefig("images/pic6.png", format = 'png')
plt.show();



t-Sne tampaknya telah membuka ruang sehingga ia benar-benar memakan skala perubahan untuk fitur-fitur yang dengan cepat berhenti berubah, yang membuat gambar tersebut benar-benar tidak informatif. Kesimpulan - jangan mencoba menyelipkan algoritma ke tempat yang tidak dimaksudkan untuk mereka .: \

Anda tidak dapat membaca lebih lanjut


Saya juga mencoba menyuntikkan tsne ke dalam untuk memvisualisasikan keadaan optimasi menengah, dengan harapan keindahan akan berubah. tapi ternyata bukan kecantikan, sampah. Jika tertarik, lihat bagaimana melakukannya. Internet adalah contoh kode injeksi yang dikotori tetapi hanya dengan menyalin mereka tidak pa karena pengganti yang terkandung di sklearn.manifold.t_sne fungsi internal _gradient_descent , dan tergantung pada versi mungkin sangat berbeda baik dalam tanda tangan dan pada perlakuan variabel internal. Jadi, cari saja sumbernya sendiri, pilih versi fungsi Anda dari sana dan masukkan hanya satu baris di dalamnya yang menambahkan dump perantara ke variabel Anda

sendiri : position.append (p.copy ()) # Kami menyimpan posisi saat ini.

Dan kemudian, seperti, kami memvisualisasikan dengan indah apa yang kami dapatkan sebagai hasilnya:

Kode injeksi
from time import time
from scipy import linalg
# This list will contain the positions of the map points at every iteration.
positions = []
def _gradient_descent(objective, p0, it, n_iter,
                      n_iter_check=1, n_iter_without_progress=300,
                      momentum=0.8, learning_rate=200.0, min_gain=0.01,
                      min_grad_norm=1e-7, verbose=0, args=None, kwargs=None):
    # The documentation of this function can be found in scikit-learn's code.
    if args is None:
        args = []
    if kwargs is None:
        kwargs = {}

    p = p0.copy().ravel()
    update = np.zeros_like(p)
    gains = np.ones_like(p)
    error = np.finfo(np.float).max
    best_error = np.finfo(np.float).max
    best_iter = i = it

    tic = time()
    for i in range(it, n_iter):
        positions.append(p.copy()) # We save the current position.
        
        check_convergence = (i + 1) % n_iter_check == 0
        # only compute the error when needed
        kwargs['compute_error'] = check_convergence or i == n_iter - 1

        error, grad = objective(p, *args, **kwargs)
        grad_norm = linalg.norm(grad)

        inc = update * grad < 0.0
        dec = np.invert(inc)
        gains[inc] += 0.2
        gains[dec] *= 0.8
        np.clip(gains, min_gain, np.inf, out=gains)
        grad *= gains
        update = momentum * update - learning_rate * grad
        p += update

        if check_convergence:
            toc = time()
            duration = toc - tic
            tic = toc

            if verbose >= 2:
                print("[t-SNE] Iteration %d: error = %.7f,"
                      " gradient norm = %.7f"
                      " (%s iterations in %0.3fs)"
                      % (i + 1, error, grad_norm, n_iter_check, duration))

            if error < best_error:
                best_error = error
                best_iter = i
            elif i - best_iter > n_iter_without_progress:
                if verbose >= 2:
                    print("[t-SNE] Iteration %d: did not make any progress "
                          "during the last %d episodes. Finished."
                          % (i + 1, n_iter_without_progress))
                break
            if grad_norm <= min_grad_norm:
                if verbose >= 2:
                    print("[t-SNE] Iteration %d: gradient norm %f. Finished."
                          % (i + 1, grad_norm))
                break

    return p, error, i

manifold.t_sne._gradient_descent = _gradient_descent

Terapkan t-SNE `` diperbaiki ''
tsne_representation = manifold.TSNE(random_state=17).fit_transform(history)
X_iter = np.dstack(position.reshape(-1, 2) for position in positions)
position_reshape = [position.reshape(-1, 2) for position in positions]
print(position_reshape[0].shape)
print('[0] min', position_reshape[0][:,0].min(),'max', position_reshape[0][:,0].max())
print('[1] min', position_reshape[1][:,0].min(),'max', position_reshape[1][:,0].max())
print('[2] min', position_reshape[2][:,0].min(),'max', position_reshape[2][:,0].max())

(41, 2)
[0] min -0.00018188123 maks 0.00027207955
[1] min -0.05136269 maks 0.032607622
[2] min -4.392309 maks 7.9074526
Nilai menari dalam rentang yang sangat luas, jadi saya akan menskalakannya sebelum menggambar. Pada siklus, semua ini dilakukan kapet secara perlahan. :(

Saya skala
%%time
from sklearn.preprocessing import MinMaxScaler
minMaxScaler = MinMaxScaler()
minMaxScaler.fit_transform(position_reshape[0])
position_reshape = [minMaxScaler.fit_transform(frame) for frame in position_reshape]
position_reshape[0].min(), position_reshape[0].max()

Menghidupkan
%%time

from matplotlib.animation import FuncAnimation, PillowWriter
#plt.style.use('seaborn-pastel')

fig = plt.figure()

ax = plt.axes(xlim=(0, 1), ylim=(0, 1))
line, = ax.plot([], [], lw=3)

def init():
    line.set_data([], [])
    return line,
def animate(i):
    x = position_reshape[i][:,0]
    y = position_reshape[i][:,1]
    line.set_data(x, y)
    return line,

anim = FuncAnimation(fig, animate, init_func=init, frames=36, interval=20, blit=True, repeat_delay = 1000)
anim.save('images/animate_tsne_learning.gif', writer=PillowWriter(fps=5))



Ini instruktif dalam hal keterampilan, tetapi sama sekali tidak berguna dalam tugas ini dan jelek.

Tentang ini saya mengucapkan selamat tinggal kepada Anda. Saya berharap gagasan bahwa bahkan dari knn Anda bisa mendapatkan sesuatu yang baru dan menarik, serta potongan-potongan kode, akan membantu Anda dan juga bersenang-senang dengan data di pesta intelektual ini selama wabah.

All Articles