Czym jest feature selection
Feature selection jak sama nazwa wskazuje, jest to dobór odpowiednich zmiennych do modelu. Stosowanie takiego podejścia pozwala nam uniknąć wielu problemów co z kolei prowadzi do uzyskania lepszej analizy.
Zalety stosowania feature selection
Poczynając od tych najważniejszych:
- Pozwala uniknąć klątwy wymiarowości
- tzw. „curse of dimensionality” odnosi się do zjawiska pojawiającego się podczas analizowania zbiorów danych o bardzo dużej liczbie wymiarów (zmiennych). Przy rozwiązywaniu problemów probabilistyczno-statystycznych niezbędna liczba obserwacji rośnie w tempie wykładniczym w stosunku do liczby zmiennych. I tak na przykład, jeżeli będziemy posiadać tyle obserwacji ile zmiennych, model może być wytrenowany na próbce uczącej doskonale, jednakże jego zdolność do generalizacji jest bardzo słaba.
- Zwiększa dokładność modelu
- mniejsza ilość danych „zwodniczych” (nie tłumaczących procesu generującego dane) tym dokładność modelu większa.
- Mniejsza liczba zmiennych w modelu pozwala na łatwiejszą wizualizacje i interpretacje wyników.
- Redukuje czas uczenia modelu
- mniejsza ilość zmiennych skutkuje mniejszą liczbą obserwacji na których uczy się model.
Metody doboru zmiennych
Istnieje wiele metod/sposobów doboru zmiennych do modelu. Niektóre z nich, różnią się od siebie niewiele inne natomiast odbiegają założeniami trochę bardziej. Wyróżnia się 3 rodzaje:
- Filter-based – za pomocą wybranej przez nas miary statystycznej, a następnie przy użyciu uzyskanych na jej podstawie wyników, tworzymy ranking zmiennych. Przykładem takich metod może być użycie testu niezależności Chi kwadrat czy współczynników korelacji.
- Wrapper-based – w tych metodach, wybór zbioru zmiennych polega na tworzeniu różnych kombinacji zbioru cech, które są oceniane i porównywane z innymi kombinacjami. Do oceny, używa się modelu predykcyjnego i miary „wbudowanej” w model. Przykładem mogą być tutaj algorytmy step-wise, gdzie modelem jest regresja liniowa a wybraną wartością doboru zmiennych może być R-kwadrat, p-value (dla istotności parametrów) lub nawet suma kwadratów reszt.
- Embedded-based – jest to zbiór metod, dokonują wyboru zmiennych optymalnych w procesie uczenia modelu. Przykładem takiej metody może być algorytm Lasso, który w wyniku regularyzacji może zredukować niektóre współczynniki przy zmiennych do zera co w rezultacie spowoduje usunięcie tych zmiennych z modelu. Innym przykładem mogą być algorytmy drzew decyzyjnych czy lasów losowych.
Przyjrzyjmy się zatem z bliska tym technikom i ich implementacji w pythonie.
Filter-based
Aby skorzystać z metod typu filter-based potrzebujemy pewnej miary. Sam jej dobór może być jednakże kłopotliwym zadaniem, ponieważ mamy takie miary jak: wsp. korelacji Pearsona, wsp. korelacji rang Spearmana, Tau Kendalla, ANOVE, Chi-kwadrat oraz Mutual Information. W celu ułatwienia doboru miary możemy się posłużyć następującym schematem:

Widzimy, że miarę po której dokonamy filtrowania możemy dobrać na podstawie typu zmiennych objaśniających oraz zmiennej celu. Np. posiadamy zmienną binarną oraz ilościowe zmienne objaśniające więc w tym wypadku możemy użyć albo miarę Tau Kendalla albo ANOVE itd.. Omówmy w takim razie jak interpretujemy powyższe miary oraz czym się różnią.
Współczynnik korelacji Pearsona
Zaczynamy od najprostszej metody doboru zmiennych z rodziny filter-based. Współczynnik korelacji Pearsona mierzy liniową korelację pomiędzy dwoma zmiennymi X oraz Y. Przyjmuje wartości z przedziału [-1:1], im bliżej wartości jedności, tym korelacja silniejsza. Ujemne wartości współczynnika wskazują na ujemną korelacji, dodatnie na dodatnią, natomiast wartości bliskie 0 wskazują na brak korelacji. Wzór oraz jego wyprowadzenie można znaleźć tutaj.
W przypadku doboru zmiennych jesteśmy zainteresowani wysoką wartością współczynnika korelacji pomiędzy zmienną celu a predyktorami. Właśnie to będzie naszym kryterium doboru cech do modelu. Należy mieć na uwadze jednak ogromny mankament współczynnika korelacji Pearsona gdyż mierzy on liniową zależność. Jeżeli w naszym modelu występuje nieliniowa zależność pomiędzy predyktorem a zmienną objaśnianą ten współczynnik nam tego nie pokaże dlatego należy być bardzo ostrożnym. Zaimplementujmy sobie metodę doboru opartą o ten współczynnik na zbiorze danych nt. jakości czerwonego wina, jako zmienną celu arbitralnie wybrałem sobie poziom alkoholu natomiast pozostałe zmienne bez zmiennej „jakość wina” wykorzystałem jako zmienne objaśniające:
import numpy as np import pandas as pd df = pd.read_csv("winequality-red.csv") X = df[list(df.columns[:-2])] y = df[df.columns[-2]] def corr_criteria(y, X, absolute_values = False): corr_dict = {} if absolute_values: for column in X.columns: corr_dict[column]= round(abs(np.corrcoef(y, X[column])[1][0]),3) else: for column in X.columns: corr_dict[column]= round(np.corrcoef(y, X[column])[1][0],3) corr_sorted = {k: v for k, v in sorted(corr_dict.items(), key=lambda item: item[1], reverse=True)} return corr_sorted corr_criteria(y,X, absolute_values = True)

Współczynnik korelacji rang Spearmana
W przypadku gdy mamy obiekcje do tego czy korelacja pomiędzy naszymi zmiennymi jest liniowa, możemy zastosować współczynnik korelacji Spearmana. Miara ta pokazuje dowolną monotoniczną zależność w tym także nieliniową, pomiędzy zmiennymi losowymi. Dlatego, nie jest w stanie wykazać takich zależności jak sezonowość w przypadku modelowania szeregów czasowych. Również przyjmuje wartości z przedziału [-1:1] oraz ma podobną interpretacje jak wsp. korelacji Pearsona. Tym razem zastosujemy funkcję spearmanr z pakietu SciPy. Zwraca ona zarówno współczynnik korelacji rang Spearmana jak i p value dla hipotezy zerowej: zmienne są skorelowane (sami autorzy jednak zalecają interpretowanie p-value jedynie na dużych zbiorach danych). Zastosujmy go na naszym zbiorze danych:
from scipy.stats import spearmanr def corr_spearman_criteria(y, X,absolute_values = False, alpha=0.05, return_pval=False): corr_pval = {} corr_val = {} for column in X.columns: if spearmanr(y, X[column])[1]>0.05: corr_pval[column]= "Zmienna jest nieskorelowana, p-val: {:.3f}".format(spearmanr(y, X[column])[1]) else: corr_pval[column]= "Zmienna jest skorelowana, p-val: {:.3f}".format(spearmanr(y, X[column])[1]) if absolute_values: corr_val[column] = round(abs(spearmanr(y, X[column])[0]),3) else: corr_val[column] = round(spearmanr(y, X[column])[0],3) corr_sorted = {k: v for k, v in sorted(corr_val.items(), key=lambda item: item[1], reverse=True)} if return_pval: return corr_pval else: return corr_sorted corr_spearman_criteria(y,X, absolute_values = True)

Nasze wyniki są bardzo zbliżone do współczynnika korelacji Pearsona.
ANOVA
Jest to metoda, którą możemy użyć w przypadku zmiennych ilościowych jak i jakościowych. Należy pamiętać jednak, że jeżeli nasza zmienna celu jest jakościowa to predyktory muszą być ilościowe, a jeżeli zmienna celu jest ilościowa to predyktory muszą być zmiennymi jakościowymi. ANOVA (analysis of variance) jak sama nazwa wskazuje jest to analiza wariancji. Wyróżniamy kilka rodzajów ANOVA co powoduje, że jest ona bardzo rozbudowanym rodzajem analizy, z pewnością pojawi się artykuł na naszym blogu, w którym szerzej zostanie opisana. Tymczasem, w tym artykule omówię tylko najważniejsze dla nas informacje, w kontekście doboru zmiennych.
W ANOVA, czynnikami określa się zmienne objaśniające, a więc jednoczynnikowa analiza odnosi się do sytuacji gdy mamy jeden predyktor a wieloczynnikowa gdy mamy ich co najmniej dwa – co jest naszym problemem w przypadku feature selection.
Może pojawić się pytanie czemu w takim razie nie zrobić analizy jednoczynnikowej dla każdej zmiennej i porównać wyniki? Otóż, w przypadku zastosowania takiej analizy nie uwzględniamy interakcji czynników między sobą. Jest to o tyle ważne, że niektóre predyktory badane oddzielnie mogą nie wykazywać wpływu na zmienną objaśnianą, natomiast gdy są badane wraz z innymi predyktorami już taki wpływ może się pojawić. Więc rezygnując z analizy wieloczynnikowej rezygnujemy również z potencjalnych informacji.
Ważnym konceptem są również tzw. wariancje wyjaśnione i niewyjaśnione. Całkowita wariancja modelu jest to suma wariancji wyjaśnionej i niewyjaśnionej.
Wariancja wyjaśniona, jest to miara rozbieżności między modelem a rzeczywistymi danymi. Jest to część całkowitej wariancji modelu, która jest rzeczywiście wyjaśniona przez czynniki. W modelu regresji liniowej tą wariancje reprezentuje wskaźnik R2, w ANOVIE jest to wskaźnik eta2 i różni się jedynie tym, że w przypadku R kwadrat mamy założenie, że zmiany zmiennej celu są spowodowane liniową zależnością z predyktorem.
Wariancja niewyjaśniona nazywana również wariancją reszt lub error variance. Istnieje wiele definicji tej wariancji, w zależności od rodzaju przeprowadzanej analizy. W ANOVA będzie ona oznaczała wariancje, która nie jest wyjaśniana przez grupy. W celu lepszego zrozumienia tego pojęcia, posłużę się przykładem. Gdybyśmy chcieli porównać wzrost kobiet i mężczyzn, to wystąpiły by różnice w obrębie poszczególnych grup i pomiędzy grupami. Jeżeli chodzi o różnice w obrębie poszczególnych grup łatwo to wyjaśnić mianowicie nie wszyscy ludzie tej samej płci mają ten sam wzrost. Natomiast, różnice pomiędzy grupami są oczywiste, kobiety przeciętnie mają inny wzrost niż mężczyźni. Takie zjawisko określa się mianem wariacji niewyjaśnionej w ANOVA. Ten rodzaj wariancji otrzymamy również poprzez różnicę pomiędzy całkowitą wariancją modelu a wariancją wyjaśnioną.
W przypadku doboru zmiennych za pomocą ANOVA korzystamy z testu F. Jest to po prostu iloraz wariancji wyjaśnionej i wariancji niewyjaśnionej. Hipoteza zerowa tego testu zakłada, że średnie z dwóch lub więcej próbek pochodzą z tego samego rozkładu. W przypadku odrzucenia H0 wiemy, że dany predyktor nie może być wykorzystywany do predykcji zmiennej celu.
Przeprowadźmy dobór zmiennych na naszym zbiorze za pomocą ANOVA. W tym celu posłużę się gotową funkcją z pakietu Sci-kit learn. SelectKBest na podstawie wskazanej miary wybiera wskazaną liczbę zmiennych najlepszych, f_classif, odpowiada testowi F dla ANOVY. Tym razem naszą zmienną celu będzie jakość wina – jest to zmienna jakościowa. Natomiast pozostałe zmienne są ilościowe, więc możemy użyć ANOVA:
from sklearn.feature_selection import SelectKBest, f_classif #zbiór danych df = pd.read_csv("winequality-red.csv") X = df[list(df.columns[:-1])] y = df[df.columns[-1]] #przekształcenie skb = SelectKBest(score_func=f_classif, k=3) #wybieramy 3 zmienne skb.fit(X,y) X_new = skb.transform(X) #nasz nowy df z wybranymi zmiennymi objaśniającymi print(X.shape,'\n', X_new.shape)

Jak widać na powyższym obrazku, z 11 zmiennych zostały wybrane tylko 3, tyle ile wskazaliśmy. Jeżeli chcemy dowiedzieć się które zmienne zostały wybrane, możemy to zrobić w następujący sposób:
#Wybrane 3 zmienne columns = X.columns[skb.get_support()] for i in columns: print(i)

Tak się prezentują nasze 3 wybrane na podstawie miary F-ANOVA zmienne. Możemy sobie wyprintować całą tabelkę ze zmiennymi i jakie wartości testu F osiągnęła każda zmienna:
#wszystkie zmienne i wyniki for i, v in zip(skb.scores_, X.columns): print("{} - wynik: {:.3f}\n".format(v,i))

Jak widać im wyższy wynik tym zmienna jest bardziej pożądana.
Tau Kendalla
Tau Kendalla podobnie jak współczynnik korelacji Pearsona oraz współczynnik korelacji rang Spearmana jest miarą monotonicznej zależności dwóch zmiennych losowych. W praktyce służy do opisu korelacji pomiędzy zmiennymi porządkowymi. Przyjmuje wartości z przedziału [-1,1]. Wartość „1” oznacza, że jeżeli wartości jednej ze zmiennych rośną to wartości drugiej również będą rosły, natomiast „-1” oznacza sytuacje odwrotną – jeżeli wartości jednej zmiennej rosną to wartości drugiej maleją. Ze względu na fakt, że miara ta opisuje korelacje pomiędzy zmiennymi porządkowymi zdecydowanie będzie rzadziej wykorzystywana do wyboru zmiennych, jednakże warto o niej pamiętać. Dla ciekawskich – miara ta jest dostępna w pakiecie SciPy: tutaj. Jeżeli chcielibyśmy ją zaimplementować, możemy skorzystać z kodu na korelacje rang Spearmana tylko w miejsce funkcji spearmana wstawić Tau Kendalla. Więcej o samym teście można przeczytać chociażby na wiki.
Test niezależności Chi-kwadrat
Jest to jeden z najczęściej wykorzystywanych testów w przypadku mierzenia zależności pomiędzy zmiennymi jakościowymi. Stosowany jest on dla tabeli kontyngencji zliczeń wystąpień danej konfiguracji zmiennych jakościowych. Przykład tabeli kontyngencji 2×2:

Jak widać taka tabela przedstawia dwie zmienne jakościowe prawdopodobnie „zwierzę” oraz „płeć”. Konstruowana jest ona poprzez zliczenia wystąpienia danej konfiguracji wartości zmiennych.
Hipoteza 0 tego testu zakłada, że testowane zmienne są od siebie niezależne. W przypadku odrzucenia tej hipotezy, możemy stwierdzić, że badane zmienne są od siebie zależne. Więcej o konstrukcji testu możemy przeczytać tutaj. W przypadku interpretacji wyników tego testu, możemy zarówno posłużyć się wartością p-value lub wybierać zmienne dla których wartości obliczonej statystyki są najwyższe.
W przykładzie implementacji testu niezależności chi-kwadrat, posłużę się zbiorem danych o nazwie „cat in the dat„. Jest to zbiór danych typu piaskownica, gdzie wszystkie 25 zmiennych jest zmiennymi jakościowymi. Jest to idealny zbiór danych dla naszego przykładu. Samego doboru zmiennych przy pomocy testu chi-kwadrat, dokonam przy użyciu znanego z przykładu ANOVA – SelectKBest. Zanim jednak to nastąpi dokonam kodowania wszystkich zmiennych jakościowych techniką „One Hot Encoding” również przy użyciu sci-kit learna (jest ona szybka jednak zwraca obiekt sparse-matrix, którego nie możemy bezpośrednio podglądać):
#wczytajmy zbiór danych 30tys obserwacji 25 zmiennych df = pd.read_csv("train.csv", index_col='id') y = df[df.columns[-1]] X = df[df.columns[:-1]] #zwolnijmy trochę pamięci del df #OneHotEncoder from sklearn.preprocessing import OneHotEncoder one = OneHotEncoder() #bardzo szybka metoda ale nie możemy zajrzeć w dane one.fit(X) X_t=one.transform(X) #otrzymamy obiekt sparse matrix o wymiarach 300000x16461!!! #Dokonujemy selekcji 5 najlepszych zmiennych z 16461 :D from sklearn.feature_selection import chi2 skb = SelectKBest(score_func=chi2, k=5) skb.fit(X_t, y) X_new = skb.transform(X_t) #Zamieniamy obiekt sparse matrix w tabelę (teraz ma 5 zmiennych) X_new = X_new.toarray() #Sprawdzamy nazwy zmiennych dobranych do modelu #dokonujemy tego przy wykorzystaniu nazw z one hote encodera one.get_feature_names()[skb.get_support()]

Powyżej nasze wybrane 5 zmiennych z pośród prawie 16,5 tys. do modelu za pomocą testu niezależności chi-kwadrat. Pierwszy przedrostek oznacza numer zmiennej w data framie, drugi natomiast kategorię tej zmiennej.
Drobna uwaga, jeżeli mamy dużo zmiennych jakościowych, które również mają dużo kategorii, a chcielibyśmy dokonać kodowania typu one hot, omijajmy szerokim łukiem metodę dummy z modułu Pandas. Chodzi w tym przypadku o optymalizację zużycia pamięci. Dzięki przekształceniu df-a w obiekt sparse matrix, scikit wykona kodowanie 16tys. zmiennych w parę sekund, natomiast Pandas może spowodować wysypanie się kernela :D.
Mutual Information
Mutual information czyli informacja wzajemna, jest pojęcięm z zakresu teorii informacji. Miara ta, wykorzystywana jest do badania wzajemnej zależności pomiędzy dwoma zmiennymi losowymi. Mierzy ona otrzymaną ilość informacji o jednej zmiennej, poprzez obserwację drugiej zmiennej. Jednostkami pomiaru są najczęściej bity, nazywane również shannons w teorii informacji. Mutual information jest blisko spokrewnione z entropią, która jest fundamentalnym pojęciem w tej teorii, a określa ona oczekiwaną ilość informacji, którą zmienna losowa przechowuje.
Intuicyjna interpretacja brzmiała by następująco: ile informacji o X, możemy poznać, znając Y.
Ważną informacją jest to, że mutual information nie ma ograniczenia co do tego aby zmienne losowe były zmiennymi ilościowymi oraz potrafi wykrywać nieliniową zależność. Implementacji dokonamy na tym samym zbiorze danych co poprzednio. Również wykorzystamy pakiet scikit-learn, w którym miara jest już zaimplementowana:
#importujemy mutual_information from sklearn.feature_selection import mutual_info_classif #dokonujemy wyboru 5 najlepszych zmiennych skb = SelectKBest(score_func=mutual_info_classif, k=5) skb.fit(X_t, y) X_new = skb.transform(X_t) #nazwy wybranych kolumn columns = one.get_feature_names()[skb.get_support()] for i in columns: print(i)

Otrzymałem takie same zmienne jak w przypadku testu Chi-kwadrat. Zauważyłem jednak, że czas obliczenia w tym przypadku był zdecydowanie dłuższy (w końcu to 16tys zmiennych). Sam fakt, że z dwóch różnych metod otrzymaliśmy te same zmienne jest dosyć dobrym wskaźnikiem, że mogą być one odpowiednie do modelowania zmiennej celu.
Jest to również ostatnia miara, którą chciałem omówić w ramach metod typu filter-based. Przejdźmy zatem do następnych technik czyli wrapped-based.
Wrapped-based
Jak wspomniałem na początku artykułu, proces doboru zmiennych dla metod typu wrapped polega użyciu pewnego algorytmu, który wykorzystuje miary dopasowania algorytmu predykcyjnego (lub innych określających jego jakość), a następnie na podstawie wartości tych miar dobierana jest optymalna konfiguracja zmiennych objaśniających. Stąd też nazwa tego typu metod wrapped, czyli opakowane. Ponieważ opakowujemy algorytmem nasz model predykcyjny. Do tego rodzaju technik należą wszystkie algorytmy stepwise: forward, backward oraz ich połączenie, ale nie tylko. Istnieją również implementacje oparte na wykorzystaniu algorytmu genetycznego, więcej o tym można poczytać np. [tutaj].
Uwaga! Istnieje wiele przesłanek mówiących, że nie powinno się w ogóle stosować metod typu stepwise w przypadku doboru zmiennych. Wymienia się kilka przyczyn:
- Metody te są algorytmami zachłannymi, więc nie są optymalne w doborze zmiennych.
- W przypadku gdy, używamy naszego zbioru danych kilkukrotnie do wyboru zmiennych a następnie chcemy dokonać wnioskowania na podstawie tych danych wprowadzamy do naszej analizy znaczące obciążenie. Taki wybór zmiennych powoduje tak naprawdę przeuczenie modelu. Więcej na ten temat można przeczytać tym artykule: [tutaj].
- Problem porównań wielokrotnych – czyli sytuacja kiedy dokonujemy estymacji lub weryfikujemy hipotezy wielokrotnie, przy porównywaniu tych samych grup. Zwiększamy wtedy prawdopodobieństwo przyjęcia fałszywej hipotezy alternatywnej. Wynika to z tego, że faktyczny przyjęty poziom istotności z każdą kolejną próbą k rośnie. I tak przy trzynastym wielokrotnym porównaniu, prawdopodobieństwo przyjęcia fałszywej hipotezy alternatywnej wynosi już 50%!!! Dlatego warto, mieć na uwadze stosowanie odpowiednich poprawek. Np. poprawka Bonferonniego. Należy o tym pamiętać, również w przypadku stosowania poprzednich metod typu filter-based.
Ze względu na te problemy, nie zaimplementuje wszystkich metod typu stepwise, a skorzystam jedynie z gotowej funkcji RFE z pakietu sci-kit learn czyli Recursive Feature Elimination. Jest to eliminacja typu backward, którą można stosować dla dowolnego modelu predykcyjnego z pakietu sci-kit. Wykorzystuje ona wbudowane w modelach funkcje mierzące istotność zmiennych.
Zasada działania jest mniej więcej taka: startujemy ze wszystkimi zmiennymi. Następnie, na podstawie tych parametrów usuwana jest najmniej istotna zmienna. W dalszym procesie kroki są powtarzane, aż do uzyskania narzuconej przez nas liczby zmiennych.
Implementacji dokonam na zbiorze danych dotyczącym wina, a zmienną celu znowu będzie poziom alkoholu. Jako algorytm predykcyjny wybrałem regresje liniową, jednakże w zależności od problemu, możemy wybrać dowolny algorytm z pakietu sci-kit:
from sklearn.feature_selection import RFE from sklearn.linear_model import LinearRegression #zbiór danych df = pd.read_csv("winequality-red.csv") y = df[df.columns[-2]] X = df[list(df.columns[:-2])] #Inicjujemy klasę RFE rfe = RFE(estimator=LinearRegression(), n_features_to_select=3) rfe.fit(X,y) X_new = rfe.transform(X) #nowe, wybrane zmienne #Wybrane 3 zmienne columns = X.columns[rfe.get_support()] for i in columns: print(i)

Tak się prezentują nasze 3 wybrane zmienne, jednakże by nie pozostać gołosłownym pokażę, że ta metoda działa z innym algorytmem np. lasami losowymi:
from sklearn.ensemble import RandomForestRegressor #tym razem random forest rfe = RFE(estimator=RandomForestRegressor(), n_features_to_select=3) rfe.fit(X,y) X_new = rfe.transform(X) #Wybrane 3 zmienne columns = X.columns[rfe.get_support()] for i in columns: print(i)

Jak powiedziałem tak zrobiłem. Algorytm jako drugą istotną wybrał inną zmienną. Taki a nie inny wybór ma związek z tym co napisałem wyżej, dla każdego algorytmu stosowana jest inna miara doboru zmiennych.
W przypadku metod wrapped to wszystko co chciałem pokazać. Sama implementacja metod stepwise nie jest aż tak skomplikowana, chociaż zdecydowanie nie polecam ich stosować. Co do pozostałych metod wrapped nie są one, aż tak popularne jak algorytmy stepwise oraz sama ich implementacja jest dosyć skomplikowana (np. algorytm genetyczny). Z tych powodów, stosuje się częściej metody typu filter-based, albo metody embedded, które opisuje w kolejnym akapicie.
Embedded-based
Proces doboru zmiennych w metodach embedded dokonywany jest już w procesie uczenia modelu. Metody te mają wiele zalet:
- Nie powielają problemów wielokrotnego testowania hipotez oraz obciążenia modelu wskutek doboru zmiennych poprzez wielokrotne uczenie na jednym zbiorze danych
- Biorą pod uwagę interakcje między zmiennymi tak samo jak metody typu wrapped
- Są szybsze od metod filter-based
- Są również bardziej dokładne
W procesie doboru zmiennych można wyróżnić 3 kroki. Na początku model jest uczony, następnie z nauczonego modelu odczytywana jest istotność zmiennych (każdy model ma swoją miarę), na końcu odrzucane są te zmienne, które mają najniższą wartość miary istotności.
Wśród tych metod najczęściej wyróżnia się dwa rodzaje algorytmów:
- oparte o Regularyzację
- oparte o Drzewa decyzyjne
Regularyzacja
Dla modeli liniowych mamy 3 główne typy regularyzacji:
- Lasso – zwane również normą L1 – feature selection
- Ridge– zwane również regresją grzbietową lub normą L2
- Elastic Net – zwane L1/L2
W przypadku feature selection będziemy korzystać jedynie z Lasso, rzadziej z Elastic Net, a z Ridge wcale. Wynika to z faktu, że metody te nakładają pewną karę na szacowane współczynniki. Regresja typu Lasso pozwala na zredukowanie wag mniej znaczących zmiennych do zera. Co powoduje, że zmienne z takimi wagami tak naprawdę zostają usunięte z modelu. Ridge natomiast jedynie zmniejsza te współczynniki ale ich nie wyzeruje. Elastic-net to połączenie obu tych metod i współczynniki nie zawsze będą redukowane do 0, dlatego tej metody też raczej nie wykorzystuje się typowo pod feature selection.
Jeżeli chodzi o stosowanie tych metod, to wykorzystuje się je w przypadku modeli liniowych i tak w sklearn dla modeli regresywnych będzie to model Lasso, natomiast dla modelu klasyfikacji będzie to regresja logistyczna (LogisticRegression) ale z wskazaną karą(penalty) = 'l1′. Dodatkowo skorzystamy z klasy SelectFromModel, która pozwala na właśnie stosowanie podejścia typu embedded.
Sprawdźmy zatem jak wygląda ich implementacja:
#Lasso i Regresja logistyczna from sklearn.linear_model import Lasso, LogisticRegression from sklearn.feature_selection import SelectFromModel # regresja logistyczna z karą L1 #solver 'saga' wspiera zarówna regresje wieloklasową jak i karę L1 sfm = SelectFromModel(LogisticRegression(C=1, penalty='l1', solver='saga', multi_class='multinomial', max_iter=5000), max_features=3) sfm.fit(X_class, y_class) #Wybrane 3 zmienne columns = X_class.columns[sfm.get_support()] print("Regresja logistyczna: ") for i in columns: print(i) #Lasso sfm = SelectFromModel(Lasso(), max_features=3) sfm.fit(X_reg, y_reg) #Wybrane 3 zmienne columns = X_reg.columns[sfm.get_support()] print("\nLasso: ") for i in columns: print(i)

Mimo tego, że wskazaliśmy, że chcemy otrzymać do 3 zmiennych, lasso wybrało nam tylko jedną zmienną, ponieważ dla reszty współczynniki zostały zredukowane do 0. Dodatkowo warto napomnieć, że przed stosowaniem regresji Lasso niezbędna jest standaryzacja zmiennych. Więcej o tym dlaczego, można przeczytać w artykule o standaryzacji [link].
Metody oparte o Drzewa decyzyjne
Do tej grupy metod zaliczamy zarówno algorytmy baggingu oparte o drzewa (lasy losowe), jak i metody boostingu takie jak xgboost. Więcej o różnicach pomiędzy tymi algorytmami można przeczytać w artykule na naszym blogu [tutaj].
Algorytmy te zaliczamy do metod embedded ze względu na fakt, że istotność zmiennych jest oceniana w trakcie uczenia modelu. W zależności od konfiguracji i rodzaju wybranego algorytmu, zmienne dobierane są na podstawie innej miary.
Do najczęściej stosowanych miar zalicza się gini impurity lub information gain dla problemów klasyfikacji oraz wariancję dla problemów regresji. W dużym skrócie, mierzona jest „czystość” węzłów po podziale na podstawie wybranej zmiennej. Załóżmy, że naszą zmienną celu jest zmienna binarna. Wybieramy pewną zmienną i na jej podstawie dzielimy zbiór na te obserwacje należące do jednej klasy i te należące do drugiej klasy. Zmienna, która rozdzieli te dwie klasy idealnie (to znaczy wszystkie obserwacje należące do jednej klasy będą w jednym podzbiorze a należące do drugiej w drugim podzbiorze) jest najbardziej pożądana. Więc wartość optymalnych zmiennych mierzona jest na podstawie zmniejszenia „zanieczyszczenia” węzła w drzewie.
Zaimplementujmy sobie drzewa decyzyjne, lasy losowe oraz xgboost. Znowu wykorzystamy zbiór dotyczący jakości wina. W przypadku regresji zmienną celu będzie poziom alkoholu, a dla klasyfikacji będzie to jakość wina.
Xgboost – klasyfikacja
from xgboost import XGBClassifier from xgboost import plot_importance #zaimplementowany wykres #zbiory danych df = pd.read_csv("winequality-red.csv") y_class = df[df.columns[-1]] #jakość wina X_class = df[list(df.columns[:-1])] y_reg = df[df.columns[-2]] #poziom alkoholu X_reg = df[list(df.columns[:-2])] #model model = XGBClassifier() model.fit(X_class, y_class) #wykres plt.rcParams['figure.figsize'] = 10,15 plot_importance(model) plt.show()

Ogromnym plusem Xgboosta jest to, że ma już zaimplementowaną funkcję pozwalającą w szybki i prosty sposób zwizualizować, które zmienne są najbardziej istotne. Te z najwyższymi wynikami, są najbardziej pożądane. Przy okazji widzimy, która miara została wykorzystana – F score.
Drzewa decyzyjne – regresja
from sklearn.tree import DecisionTreeRegressor #model model = DecisionTreeRegressor() model.fit(X_reg, y_reg) for i,v in enumerate(model.feature_importances_): print('Zmienna: {}, Wynik: {:.3f}'.format(X_reg.columns[i],v))

Lasy losowe – klasyfikacja
from sklearn.ensemble import RandomForestClassifier #model model = RandomForestClassifier() model.fit(X_class, y_class) for i,v in enumerate(model.feature_importances_): print('Zmienna: {}, Wynik: {:.3f}'.format(X_class.columns[i],v))

Jak widzimy, implementacja tych metod jest bardzo łatwa. Oczywiście, możemy wybierać dowolne ustawienia tych modeli, czego nie zrobiłem ponieważ chciałem jedynie je zaprezentować. Drobna uwaga dotycząca Xgboosta – algorytm ten, możemy jedynie stosować dla ciągłych zmiennych objaśniających, jednak istnieje alternatywa: CatBoost. Polecam zapoznać się z dokładniejszą dokumentacją i przykładami stosowania tego algorytmu [tutaj].
Dalsze rozważania
W tym akapicie chciałbym poruszyć pewne tematy związane z feature selection, które uważam za istotne.
Problem nieuniknionego biasu modelu
Jest to problem, o którym wspominałem w kontekście metod typu wrapper. W sytuacji, gdy dokonujemy wyboru zmiennych za pomocą różnorakich metod na tym samym zbiorze, na którym będziemy uczyć nasz model wprowadzamy nieumyślnie bias. Należy o tym pamiętać nie tylko gdy stosujemy metody wrapper, ale również filter czy embedded (np. zmieniając kilka razy konfiguracje Lasso, żeby odrzucił więcej zmiennych). Przykładowym rozwiązaniem takiej zagwozdki, może być wdrożenie procesu feature selection na etapie model selection. Czyli momencie kiedy dzielimy zbiór danych na uczący oraz testowy, warto dokonać doboru zmiennych na zupełnie innym wydzielonym zbiorze danych. Jeżeli chcemy zastosować walidacje krzyżową, to proces feature selection musimy zamieścić w wewnętrznej pętli. Musi być on dokonany na foldzie dokładnie przed trenowaniem modelu. Błędem byłoby na początku przeprowadzenie feature selection na całym zbiorze, a potem dokonanie walidacji krzyżowej. Czyli wybór cech mamy dokonany przed modelowaniem. Taka decyzja może spowodować, że w procesie uczenia, model wskaże bardzo dobre wyniki na tych wybranych zmiennych, ale nie uwzględniamy wtedy w analizie innych modeli, które uczyłyby się na całym zbiorze i dopiero odrzucały pewne zmienne, a mogłyby się okazać lepsze. To jest właśnie nieświadome wprowadzenie biasu do modelu.
Problem współliniowości i rankingi metod typu filter
Zmienne współliniowe (kolinearne, redundant variables) – są to zmienne, które „przenoszą/przechowują” informacje innej zmiennej (dotyczy to zmiennych objaśniających). Wiele metod typu filter, przy sporządzaniu rankingu, ma tendecje do wyboru zmiennych współliniowych co teoretycznie jest złe, bo dwie zmienne, które przenoszą tę samą informację wpływają negatywnie na jakość modelu. Jednakże, wiele osób z pewnością zadaje sobie pytanie: czy dodanie zmiennych które są prawdopodobnie współliniowe może polepszyć nasz model?. Na to pytanie można udzielić odpowiedzi: „to zależy..”. Zmienne, które są perfekcyjnie skorelowane (wsp. korelacji=1 lub -1), są idealnie współliniowe, czyli, nie wniosą do modelu żadnej dodatkowej informacji. Z drugiej strony, nawet bardzo wysoka korelacja pomiędzy zmiennymi (ale nie równa 1 lub -1) nie oznacza, braku ich komplementarności. Takie przypadki są bardzo trudne i należałoby je rozważać indywidualnie.
Zmienne samotnie bezużyteczne
Kolejnym problemem w przypadku dobierania zmiennych są zmienne, które rozważane pojedynczo są nieistotne ale gdybyśmy je rozważali w zestawie innych zmiennych stają się istotne. Ten problem dotyczy metod, w których porównujemy zmienne objaśniające z zmienną celu indywidualnie. Wiele metod typu filter based oparte o testy statystyczne cierpi na ten problem. Jedynie wieloczynnikowa ANOVA jest odporna na ten problem. Również metody stepwise mogą być zwodnicze w tej materii. Dlatego warto się zastanowić nad doborem techniki, a najlepiej wybrać kilka różnych metod i porównać wyniki.
Czego nie poruszyliśmy
Omawiając temat feature selection, kroczymy blisko redukcji wymiaru datasetu. Według mnie, metody redukcji wymiaru a metody feature selection mają zupełnie inne zadanie, dlatego też ich nie omawiałem. Do najpopularniejszych metod dimension reduction należy z pewnością PCA (Principal Component Analysis) – czyli analiza głównych składowych. Za jej pomocą, można utworzyć główne składowe (principal components), które można w niektórych przypadkach intepretować oraz wykorzystać jako metodę feature selection. Postaramy się omówić tę metodę w najbliższym czasie w innym artykule.
Wspomniałem krótko również o metodach, które jako funkcję wrappera wykorzystują algorytm genetyczny. Jest to również ciekawy trop, można znaleźć kilka artykułów gdzie zarówno od strony teoretycznej jak i praktycznej omawiane są różne połączenia algorytmu genetycznego w celu doboru zmiennych.
10 kroków na rozwiązanie problemu z doborem zmiennych
Jako ostatnie parę słów chciałbym omówić pewną listę, która powinna pomóc w problemach z feature selection. W artykule „An introduction to Variable and Feature Selection” autorzy I. Guyon oraz A. Elisseeff, stworzyli listę-poradnik, która może posłużyć jako swego rodzaju metodyka w przypadku rozwiązywania problemów doboru zmiennych. Lista kroków prezentuje się w następujący sposób:
- Czy posiadasz wiedzę ekspercką z badanej dziedziny? – jeżeli tak zbuduj zbiór danych w oparciu o tę wiedzę.
- Czy twoje zmienne są wyrażone na tej samej skali? – jeżeli nie, rozważ ich normalizację.
- Czy podejrzewasz, że zmienne są współzależne? – jeżeli tak, poszerz swój zbiór cech poprzez stworzenie kombinacji zmiennych np. iloczyny takich zmiennych lub ich ważone sumy.
- Czy potrzebujesz zmniejszać ilość zmiennych wejściowych? (np. przez koszty, prędkość estymacji lub z powodów zrozumienia danych) – jeżeli nie https://becejprevoz.com/zyrova/index.html , stwórz nowe zmienne na podstawie dotychczasowych – porozdzielaj na podstawie pewnych właściwości lub stwórz ich ważone sumy.
- Czy musisz oceniać zmienne indywidualnie? (np. zrozumieć ich wpływ na badany problem lub dlatego, że ich liczba jest przytłaczająca nie jesteś w stanie tego zrobić) – jeżeli tak, użyj metod które pozwolą Ci stworzyć ranking zmiennych (np. filter albo embedded), jeżeli nie zrób to i tak by otrzymać informację od czego zacząć.
- Potrzebujesz predyktora?(algorytmu predykcyjnego) – jeżeli nie, zatrzymaj się tutaj.
- Podejrzewasz, że twoje dane są zanieczyszczone? (obserwacje odstające, braki danych itd.) – jeżeli tak, wykryj obserwacje odstające przy pomocy zmiennych wybranych na podstawie rankingu z podpunktu 5. Sprawdź czy mogą być wynikiem błędu(wyrzuć) czy są poprawne (zostaw).
- Czy wiesz czego spróbować najpierw? – jeżeli nie, zastosuj predyktor liniowy ( lasso, regresja logistyczna z L1, RFE, forward). Utwórz podzbiór wybranych cech. Czy możesz uzyskać podobny lub lepszy wynik na mniejszym zbiorze cech? Jeżeli tak, spróbuj użyć nieliniowych predyktorów (lasy losowe, xgboost, drzewa).
- Czy masz nowe pomysły, czas, moce obliczeniowe i wystarczające liczbę obserwacji w zbiorze? – jeżeli tak, porównaj kilka wybranych metod doboru zmiennych, wprowadź swój nowy pomysł. sprawdź korelacje zarówno liniowe jak i nieliniowe, zrób selekcje RFE oraz skorzystaj z metod embedded. Wybierz najlepsze podejście z wypróbowanych.
- Potrzebujesz stabilnego rozwiązania? (by poprawić jakość i zrozumienie analizy) – jeżeli tak, użyj metod próbkowania i wykonaj ponownie swoje analizy na kilku 'bootstrapach’.
Podsumowanie
Myślę, że to co zostało przedstawione w niniejszym artykule zaspokoi głód wiedzy niejednego śmiałka poszukującego informacji na temat doboru zmiennych do modelu. Jak wspomniałem parokrotnie, nie są to wszystkie dostępne metody oraz ich implementacje. Uważam jednak, że zamieszczone materiały pozwolą na obranie punktu wyjściowego do dalszego poszerzania wiedzy z tej dziedziny.