اختيار أهمية الميزات لأقرب الجيران (جيدًا أو معلمات مفرطة أخرى) حسب أصل مشابه للتدرج

إن الهراء الحقيقي لا يمكن أن يحقق المستحيل فحسب ، بل يعمل أيضًا كمثال تحذيري

عند تجربة أبسط مهمة للتعلم الآلي ، وجدت أنه سيكون من المثير للاهتمام اختيار 18 معلمات مفرطة في نفس الوقت في نطاق واسع إلى حد ما. في حالتي ، كان كل شيء بسيطًا جدًا بحيث يمكن إنجاز المهمة بقوة الكمبيوتر الغاشمة.

عند تعلم شيء ما ، قد يكون من المثير للاهتمام اختراع نوع من الدراجات. في بعض الأحيان ، يتبين لنا أن نخرج بشيء جديد. في بعض الأحيان اتضح أن كل شيء اخترع قبلي. ولكن حتى لو كررت المسار الذي قطعته أمامي لفترة طويلة ، فإن المكافأة غالبًا ما أفهم الآليات الأساسية لخوارزميات قدراتها وحدودها الداخلية. الذي أدعوك إليه.

في Python و DS ، بشكل معتدل ، أنا مبتدئ ، وأقوم بالعديد من الأشياء التي يمكن تنفيذها في فريق واحد وفقًا لعادتي البرمجية القديمة ، والتي يعاقبها Python بالتباطؤ ، ليس في بعض الأحيان ، ولكن بأوامر من الحجم. لذلك ، أقوم بتحميل كل الكود الخاص بي إلى المستودع. إذا كنت تعرف كيفية تنفيذها بشكل أكثر فاعلية - فلا تخجل أو تعدل هناك أو تكتب في التعليقات. https://github.com/kraidiky/GDforHyperparameters

أولئك الذين هم بالفعل فنان بيانات رائع وجربوا كل شيء في هذه الحياة سيكونون مثيرين للاهتمام ، على ما أعتقد ، تصور عملية التعلم ، التي لا تنطبق فقط على هذه المهمة.

صياغة المشكلة


هناك دورة جيدة من DS من ODS.ai وهناك تصنيف المحاضرة الثالثة ، أشجار القرار وطريقة أقرب الجيران . هناك ، يظهر على البيانات البسيطة للغاية وربما الاصطناعية كيف أن أبسط شجرة قرار تعطي دقة 94.5 ٪ ، ونفس الطريقة البسيطة للغاية لأقرب الجيران تعطي 89 ٪ بدون أي معالجة مسبقة

استيراد وتحميل البيانات
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()

قارن الخشب مع 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)

نفس 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)
في هذه المرحلة ، شعرت بالأسف على knn الذي كان من الواضح أنه غير أمين ، لأنه لم يكن لدينا عمل مع المقياس. لم أفكر بعقلي ، لقد أخذت ميزة الميزانيات من الشجرة وقمت بتطبيع المدخلات إليها. وبالتالي ، كلما كانت الميزة أكثر أهمية ، زادت مساهمتها في المسافة بين النقاط.

نحن نطعم البيانات المقيسة لأهمية الميزات
%%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)))

5إجمالي دقائق اليوم0.270386
17مكالمات خدمة العملاء0.147185
8مجموع عشية الدقائق0.135475
2الخطة الدولية0.097249
السادس عشرإجمالي الرسوم الدولية0.091671
خمسة عشرإجمالي المكالمات الدولية09.090008
4عدد رسائل vmail0.050646
10إجمالي رسوم عشية0.038593
7إجمالي رسوم اليوم0.026422
3خطة البريد الصوتي0.017068
أحد عشرإجمالي دقائق الليل0.014185
ثلاثة عشرإجمالي رسوم الليلة0.005742
12إجمالي المكالمات الليلية0.005502
9إجمالي المكالمات عشية0.003614
6إجمالي مكالمات اليوم0.002246
14إجمالي الدقائق الدولية0.002009
0Account length0.001998
1Area code0.000000

{'n_neighbors': 5} 0.909129875696528 0.913

شاركت الشجرة قليلًا من المعرفة مع knn والآن نرى 91٪. وهذا ليس بعيدًا عن 94.5٪ من شجرة الفانيليا. ثم جاءت لي فكرة. ولكن كيف ، في الواقع ، نحن بحاجة إلى تطبيع المدخلات بحيث يظهر knn أفضل نتيجة؟

أولاً ، سوف نقدر في أذهاننا مقدار ما سيعتبره الآن "جبهته". 18 معلمة ، لكل منها نقوم بها ، على سبيل المثال ، 10 خطوات محتملة للعوامل في المقياس اللوغاريتمي. نحصل على خيارات 10e18. خيار واحد مع كل عدد فردي ممكن من الجيران هو أقل من 10 والتحقق المتبادل هو 10 أيضًا ، أعتقد حوالي 1.5 ثانية. اتضح 42 ​​مليار سنة. ربما يجب التخلي عن فكرة ترك الحساب ليلا. :) وفي مكان ما هنا فكرت ، "يا! لذا سأصنع دراجة ستطير! "

بحث متدرج


في الواقع ، هذه المهمة على الأرجح لديها حد أقصى متاح فقط. حسنًا ، هذا ليس بالطبع مجالًا جيدًا من النتائج الجيدة ، لكنهما متشابهان إلى حد كبير. لذلك ، يمكننا فقط السير على طول التدرج والعثور على النقطة الأكثر ملاءمة. كان الفكر الأول هو تعميم الخوارزمية الجينية ، ولكن هنا لا يبدو أن التضاريس التكيفية متقاطعة للغاية ، وسيكون هذا مبالغة قليلاً.

سأحاول القيام بذلك يدويًا لبداية. لدفع العوامل كمعلمات مفرطة ، أحتاج إلى التعامل مع المتسللين. في المثال السابق ، كما في الدرس ، استخدمت StandartScaler ، التي تمركزت عينة التدريب في المتوسط ​​وجعلت سيجما = 1. من أجل قياسها بشكل جيد داخل خط الأنابيب ، يجب جعل المعلمة المفرطة أكثر صعوبة. لقد بدأت في البحث عن شيء مناسب لحالتي بين المحولين الكذب في sklearn.pococessing ، لكنني لم أجد أي شيء. لذلك ، حاولت أن ترث من StandartScaler عن طريق تعليق مجموعة إضافية من العوامل عليه.

فئة للتسمية ثم الضرب حسب المقياس متوافق قليلاً مع خط أنابيب 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)

تحاول تطبيق هذه الفئة
%%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__normalization': Name: important، dtype: float64}، 0.909558508358337، 0.913)

النتيجة مختلفة قليلاً عن توقعاتي. حسنا ، هذا ، من حيث المبدأ ، كل شيء يعمل. فقط لفهم هذا ، كان علي إعادة إنتاج هذا الفصل مع كل الشجاعة من الصفر في غضون ثلاث ساعات ، وعندها فقط أدركت أن الطباعة لا تتم طباعتها ليس لأن sklearn تم بطريقة خاطئة ، ولكن لأن GridSearchCV يخلق نسخًا في التدفق الرئيسي ، ولكن يقوم بتكوينهم وتدريبهم في سلاسل رسائل أخرى. وكل شيء تطبعه في تدفقات أخرى يختفي في طي النسيان. ولكن إذا وضعت n_jobs = 1 ، فسيتم عرض جميع الاستدعاءات للوظائف الملغاة على أنها لطيفة. جاءت المعرفة باهظة الثمن ، والآن لديك أيضًا ، ودفعت ثمنها من خلال قراءة مقال ممل.

حسنًا ، دعنا ننتقل. الآن أريد أن أعطي بعض التباين لكل من معلماتها ، ثم أعطها أقل قليلاً حول أفضل قيمة وهكذا حتى أحصل على نتيجة مشابهة للواقع. سيكون هذا أول خط أساس وقح لما يجب أن يحصل في النهاية على خوارزمية أحلامي.

سوف أقوم بتشكيل عدة خيارات لإعادة التوازن ، تختلف في العديد من المعلمات
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   ,               .

حسنًا ، مجموعة البيانات للتجربة الأولى جاهزة. سأحاول الآن تجربة البيانات ، كبداية من خلال البحث الشامل للخيارات الخمسة عشر الناتجة.

نجري اختيارًا تجريبيًا للمعلمات كما هو الحال في المقالة
%%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))

حسنًا ، كل شيء سيئ ، تم قضاء الوقت في تحقيق اختراق ، والنتيجة غير مستقرة للغاية. ويمكن رؤية هذا أيضًا من فحص X_holdout ، حيث ترقص النتيجة كما هو الحال في مشكال مع تغييرات طفيفة في بيانات الإدخال. سأحاول اتباع نهج مختلف. سوف أقوم بتغيير معلمة واحدة فقط في كل مرة ، ولكن مع تكتم أكبر.

أغير خاصية 4TH
%%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__normalization': 4 0.079957 الاسم :همية ، dtype: float64} ، 0.9099871410201458 ، 0.913)

حسنًا ، ماذا لدينا مع أوزة؟ تحولات من واحد إلى اثنين من عشرة في المائة عند التحقق المتبادل ، ونسبة نصف في المائة تقفز على X_holdout إذا نظرت إلى ملكية مختلفة متأثرة. يبدو أنه من الضروري والرخيص تحسين الوضع إذا بدأت بحقيقة أن الشجرة تعطينا أنه من المستحيل على مثل هذه البيانات. لكن لنفترض أنه ليس لدينا توزيع مبدئي ومعروف للوزن ، ونحاول أن نفعل نفس الشيء عند نقطة عشوائية في الدورة بخطوات صغيرة. من المثير للاهتمام ما سنأتي إليه.

تعبئة أولية
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)))]

وظيفة تغير قليلاً معلمة واحدة (مع سجلات التصحيح)
%%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)

لم أحسّن أي شيء في أي مكان ، ونتيجة لذلك ، اتخذت الخطوة الحاسمة التالية لمدة نصف ساعة تقريبًا:

نص مخفي
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)

الدقة الناتجة من knn: 91.9٪ أفضل مما كانت عليه عندما تمزق البيانات من الشجرة. وأفضل بكثير من النسخة الأصلية. قارن ما لدينا بأهمية الميزات وفقًا لشجرة القرارات:

تصور أهمية الميزات حسب 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





يبدو انه؟ نعم ، على ما يبدو. لكن أبعد ما يكون عن التطابق. ملاحظة مثيرة للاهتمام. هناك العديد من الميزات في مجموعة البيانات التي تكرر بعضها البعض تمامًا ، على سبيل المثال ، "إجمالي دقائق الليل" و "إجمالي تكلفة الليل". لذا انتبه ، فقد شاهد knn نفسه جزءًا كبيرًا من هذه الميزات المتكررة.

احفظ النتائج في ملف ، وإلا فإنه من غير المناسب العودة إلى العمل ....
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')

الموجودات


حسنًا ، إن النتيجة .919 في حد ذاتها ليست سيئة للمعرفة ، هناك أخطاء أقل 1.5 مرة من إصدار الفانيليا وأقل بنسبة 7٪ مما كانت عليه عندما أخذنا شجرة الميزة المهمة للقيادة. لكن الشيء الأكثر إثارة للاهتمام هو أنه لدينا الآن ميزة الميزة وفقًا لمعرفة knn نفسها. إنه يختلف إلى حد ما عما أخبرتنا به الشجرة. على سبيل المثال ، لدى الشجرة و knn آراء مختلفة حول أي من العلامات غير مهمة بالنسبة لنا على الإطلاق.

حسنًا ، في النهاية. حصلنا على شيء جديد وغير عادي نسبيًا ، حيث لدينا احتياطي من المعرفة بثلاث محاضرات mlcourse.ai

ods و Google للإجابة على أسئلة بسيطة حول الثعبان. في رأيي ، ليس سيئا.

شرائح الآن


من النتائج الثانوية لعمل الخوارزمية المسار الذي سلكته. صحيح ، المسار ذو 18 بُعدًا ، مما يعيق وعيه قليلاً ، جيدًا ، ليتبع في الوقت الفعلي ما تفعله الخوارزمية هناك ، وتعلم أو استخدام القمامة ليس مناسبًا جدًا. وفقًا لجدول الأخطاء ، هذا في الواقع ليس مرئيًا دائمًا. قد لا يتغير الخطأ بشكل ملحوظ لفترة طويلة ، ولكن الخوارزمية مشغولة للغاية ، تزحف على طول وادي ضيق طويل في الفضاء التكيفي. لذلك ، سأطبق ، بالنسبة للمبتدئين ، أول نهج أبسط ولكنه غني بالمعلومات تمامًا - أقوم بشكل عشوائي بإسقاط مساحة 18 الأبعاد على مساحة ثنائية الأبعاد بحيث تكون مساهمات جميع المعلمات ، بغض النظر عن أهميتها ، مفردة. في الواقع ، المسار ذو الأبعاد 18 صغير جدًا ، في مقالتنا نظرة خاطفة على رميات شبكة عصبية كما أعجبت بمساحة موازين جميع المشابك العصبية التي كانت موجودة في الشبكة العصبية وكانت جميلة وغنية بالمعلومات.

قرأت البيانات من الملف ، إذا عدت إلى العمل ، بعد اجتياز مرحلة التدريب نفسها
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))

يتوقف خطأ التحقق من الصحة عن التغيير من نقطة ما. هنا سيكون من الممكن التوقف عن التوقف التلقائي للتعلم واستخدام الوظيفة المستلمة لبقية حياتي ، لكن لدي بالفعل القليل من الوقت. :(

نحدد كم للدراسة.
last = history_holdout_score[-1]
steps = np.arange(0, history_holdout_score.shape[0])[history_holdout_score != last].max()
print(steps/18)

35.5555555555555556
قمنا بتغيير معلمة واحدة في كل مرة ، لذلك تتكون دورة التحسين من 18 خطوة. اتضح أن لدينا 36 خطوة ذات معنى ، أو شيء من هذا القبيل. الآن دعنا نحاول تصور المسار الذي تم من خلاله تدريب الطريقة.


نص مخفي
%%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()



يمكن ملاحظة أنه تم إكمال جزء كبير من الرحلة في الخطوات الأربع الأولى. دعونا نلقي نظرة على بقية الطريق مع الزيادة

بدون أول 4 نقاط
plt.plot(points[4:36,0],points[4:36,1]);
plt.savefig("images/pic3.png", format = 'png')



دعونا نلقي نظرة فاحصة على الجزء الأخير من المسار ونرى ما فعله المعلم بعد الوصول إلى وجهتها.

يقترب
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()





يمكن ملاحظة أن الخوارزمية يتم تدريبها باهتمام. حتى يجد وجهته. النقطة المحددة ، بالطبع ، تعتمد على التوزيع العشوائي في التحقق المتقاطع. ولكن بغض النظر عن النقطة المحددة ، فإن الصورة العامة لما يحدث مفهومة.

بالمناسبة ، كنت أستخدم مثل هذا الجدول الزمني لإثبات عملية التعلم.
لا يتم عرض المسار بالكامل ، ولكن الخطوات القليلة الأخيرة مع انزلاق المقياس. يمكن العثور على مثال في مقالتي الأخرى ، "We Spy on the Throws of Neural Network". ونعم ، بالطبع ، كل من يواجه مثل هذا التصور يسأل على الفور لماذا جميع العوامل لها نفس الوزن والأهمية ، ثم لها مختلفة. في المرة الأخيرة في المقالة ، حاولت إعادة النظر في أهمية المشابك واتضح أنها أقل إفادة.

هذه المرة ، مسلحًا بمعرفة جديدة ، سأحاول استخدام t-SNE لنشر مساحة متعددة الأبعاد في إسقاط يمكن أن يكون فيه كل شيء أفضل.

تي 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 قد كشفت المساحة بحيث أكلت تمامًا حجم التغييرات لتلك الميزات التي توقفت عن التغيير بسرعة ، مما جعل الصورة غير مألوفة تمامًا. الخلاصة - لا تحاول أن تنزلق الخوارزميات إلى أماكن غير مخصصة لها: \

لا يمكنك قراءة المزيد


حاولت أيضًا حقن tsne في الداخل لتصور حالات التحسين المتوسطة ، على أمل أن يتحول الجمال. لكن اتضح أنه ليس جمالًا ، بعض القمامة. إذا كنت مهتمًا ، فراجع كيفية القيام بذلك. وتناثرت الإنترنت أمثلة على هذه المدونة عن طريق الحقن ولكن ببساطة عن طريق نسخ لم يفعلوا سنويا بوت بسبب بديلا الواردة في sklearn.manifold.t_sne وظيفة داخلية _gradient_descent ، وذلك اعتمادا على الإصدار قد تكون مختلفة جدا سواء في التوقيع وبشأن معالجة المتغيرات الداخلية. حتى تجد المصادر بنفسك ، اختر نسختك من الوظيفة من هناك وأدخل سطرًا واحدًا فقط فيها يضيف مقالب وسيطة إلى

متغيرك الخاص : المواضع. append (p.copy ()) # نحفظ الموضع الحالي.

ثم ، على سبيل المثال ، نتصور بشكل جميل ما نحصل عليه نتيجة لذلك:

كود الحقن
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

تطبيق t-SNE `` الثابت ''
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 max 0.00027207955
[1] min -0.05136269 max 0.032607622
[2] min -4.392309 max 7.9074526
القيم ترقص في نطاق واسع جدًا ، لذا سأقوم بتحجيمها قبل رسمها. على دورات ، يتم كل هذا بطانات. :(

أنا مقياس
%%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()

تحريك
%%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))



إنها مفيدة من حيث المهارات ، ولكنها عديمة الفائدة على الإطلاق في هذه المهمة وقبيحة.

على هذا أقول وداعا لك. آمل أن تساعدك فكرة أنه حتى من knn يمكنك الحصول على شيء جديد ومثير للاهتمام ، بالإضافة إلى أجزاء من التعليمات البرمجية ، ستساعدك بالإضافة إلى الاستمتاع بالبيانات في هذا العيد الفكري أثناء الطاعون.

All Articles