Wprowadzenie
Czasami mylony z regresji liniowej przez nowicjuszy – ze względu na udostępnienie terminu regresja - regresja logistyczna bardzo różni się od regresji liniowej. Podczas gdy regresja liniowa przewiduje wartości takie jak 2, 2.45, 6.77 lub wartości ciągłe, dzięki czemu jest to regresja algorytm, regresja logistyczna przewiduje wartości takie jak 0 lub 1, 1 lub 2 lub 3, które są dyskretne wartości, dzięki czemu jest to klasyfikacja algorytm. Tak, to się nazywa regresja ale jest klasyfikacja algorytm. Więcej o tym za chwilę.
Dlatego jeśli Twój problem z nauką o danych dotyczy wartości ciągłych, możesz zastosować a regresja algorytm (regresja liniowa jest jednym z nich). W przeciwnym razie, jeśli wiąże się to z klasyfikacją danych wejściowych, wartości dyskretnych lub klas, można zastosować a klasyfikacja algorytm (regresja logistyczna jest jednym z nich).
W tym przewodniku będziemy wykonywać regresję logistyczną w Pythonie za pomocą biblioteki Scikit-Learn. Wyjaśnimy również, dlaczego słowo "regresja" jest obecny w nazwie i jak działa regresja logistyczna.
W tym celu najpierw załadujemy dane, które zostaną sklasyfikowane, zwizualizowane i wstępnie przetworzone. Następnie zbudujemy model regresji logistycznej, który zrozumie te dane. Model ten zostanie następnie oceniony i wykorzystany do przewidywania wartości na podstawie nowych danych wejściowych.
Motywacja
Firma, w której pracujesz, nawiązała współpracę z tureckim gospodarstwem rolnym. Ta współpraca obejmuje sprzedaż nasion dyni. Pestki dyni są bardzo ważne w żywieniu człowieka. Zawierają dobrą proporcję węglowodanów, tłuszczu, białka, wapnia, potasu, fosforu, magnezu, żelaza i cynku.
W zespole zajmującym się analizą danych Twoim zadaniem jest odróżnienie rodzajów nasion dyni na podstawie samych danych – lub klasyfikacja dane według rodzaju nasion.
Turecka farma pracuje z dwoma rodzajami nasion dyni, jeden nazywa się erçevelik i inni Ürgup Sivrisi.
Aby sklasyfikować pestki dyni, Twój zespół śledził artykuł z 2021 r. „Wykorzystanie metod uczenia maszynowego w klasyfikacji nasion dyni (Cucurbita pepo L.). Zasoby genetyczne i ewolucja upraw” z Koklu, Sarigil i Ozbek – w tym artykule jest metodologia fotografowania i wyodrębniania pomiarów nasion z obrazów.
Po zakończeniu procesu opisanego w artykule wyodrębniono następujące pomiary:
- Obszar – liczba pikseli w granicach pestki dyni
- Obwód – obwód w pikselach pestki dyni
- Długość osi głównej – również obwód w pikselach pestki dyni
- Długość osi podrzędnej – mała odległość osi pestki dyni
- Ekscentryczność – ekscentryczność pestki dyni
- Obszar wypukły – liczba pikseli najmniejszej wypukłej muszli w obszarze utworzonym przez pestkę dyni
- Stopień – stosunek obszaru pestek dyni do pikseli obwiedni
- Równoważna średnica – pierwiastek kwadratowy z pomnożenia powierzchni pestki dyni przez cztery podzielony przez pi
- Ścisłość – proporcja powierzchni pestki dyni w stosunku do powierzchni koła o tym samym obwodzie
- Solidność – wypukły i wypukły stan pestek dyni
- Okrągłość – owalność pestek dyni bez uwzględnienia zniekształceń brzegów
- Aspect Ratio – proporcje pestek dyni
To są pomiary, z którymi musisz pracować. Oprócz pomiarów istnieje również Klasa etykieta dla dwóch rodzajów pestek dyni.
Aby rozpocząć klasyfikację nasion, zaimportujmy dane i zacznijmy się im przyglądać.
Zrozumienie zbioru danych
Uwaga: Możesz pobrać zestaw danych dyni tutaj.
Po pobraniu zestawu danych możemy go załadować do struktury dataframe za pomocą pandas
biblioteka. Ponieważ jest to plik Excel, użyjemy read_excel()
metoda:
import pandas as pd
fpath = 'dataset/pumpkin_seeds_dataset.xlsx'
df = pd.read_excel(fpath)
Po załadowaniu danych możemy szybko rzucić okiem na pierwsze 5 wierszy za pomocą head()
metoda:
df.head()
To skutkuje:
Area Perimeter Major_Axis_Length Minor_Axis_Length Convex_Area Equiv_Diameter Eccentricity Solidity Extent Roundness Aspect_Ration Compactness Class
0 56276 888.242 326.1485 220.2388 56831 267.6805 0.7376 0.9902 0.7453 0.8963 1.4809 0.8207 Çerçevelik
1 76631 1068.146 417.1932 234.2289 77280 312.3614 0.8275 0.9916 0.7151 0.8440 1.7811 0.7487 Çerçevelik
2 71623 1082.987 435.8328 211.0457 72663 301.9822 0.8749 0.9857 0.7400 0.7674 2.0651 0.6929 Çerçevelik
3 66458 992.051 381.5638 222.5322 67118 290.8899 0.8123 0.9902 0.7396 0.8486 1.7146 0.7624 Çerçevelik
4 66107 998.146 383.8883 220.4545 67117 290.1207 0.8187 0.9850 0.6752 0.8338 1.7413 0.7557 Çerçevelik
Tutaj mamy wszystkie pomiary w odpowiednich kolumnach, nasz cechy, a także Klasa kolumna, nasza cel, który jest ostatnim w ramce danych. Możemy zobaczyć, ile mamy pomiarów za pomocą shape
atrybut:
df.shape
Wynik to:
(2500, 13)
Wynik kształtu mówi nam, że w zestawie danych jest 2500 wpisów (lub wierszy) i 13 kolumn. Ponieważ wiemy, że istnieje jedna kolumna docelowa – oznacza to, że mamy 12 kolumn funkcji.
Możemy teraz zbadać zmienną docelową, nasiona dyni Class
. Ponieważ będziemy przewidywać tę zmienną, warto zobaczyć, ile mamy próbek każdego nasiona dyni. Zwykle im mniejsza różnica między liczbą wystąpień w naszych klasach, tym bardziej zrównoważona jest nasza próba i tym lepsze nasze przewidywania.
Tę kontrolę można przeprowadzić, licząc każdą próbkę nasion za pomocą value_counts()
metoda:
df['Class'].value_counts()
Powyższy kod wyświetla:
Çerçevelik 1300
Ürgüp Sivrisi 1200
Name: Class, dtype: int64
Widzimy, że istnieje 1300 próbek erçevelik nasion i 1200 próbek Ürgup Sivrisi nasionko. Zauważ, że różnica między nimi wynosi 100 próbek, bardzo mała różnica, co jest dla nas dobre i wskazuje, że nie ma potrzeby ponownego równoważenia liczby próbek.
Przyjrzyjmy się również opisowym statystykom naszych funkcji za pomocą describe()
metoda, aby zobaczyć, jak dobrze rozłożone są dane. Dokonamy również transpozycji wynikowej tabeli za pomocą T
aby ułatwić porównywanie statystyk:
df.describe().T
Wynikowa tabela to:
count mean std min 25% 50% 75% max
Area 2500.0 80658.220800 13664.510228 47939.0000 70765.000000 79076.00000 89757.500000 136574.0000
Perimeter 2500.0 1130.279015 109.256418 868.4850 1048.829750 1123.67200 1203.340500 1559.4500
Major_Axis_Length 2500.0 456.601840 56.235704 320.8446 414.957850 449.49660 492.737650 661.9113
Minor_Axis_Length 2500.0 225.794921 23.297245 152.1718 211.245925 224.70310 240.672875 305.8180
Convex_Area 2500.0 81508.084400 13764.092788 48366.0000 71512.000000 79872.00000 90797.750000 138384.0000
Equiv_Diameter 2500.0 319.334230 26.891920 247.0584 300.167975 317.30535 338.057375 417.0029
Eccentricity 2500.0 0.860879 0.045167 0.4921 0.831700 0.86370 0.897025 0.9481
Solidity 2500.0 0.989492 0.003494 0.9186 0.988300 0.99030 0.991500 0.9944
Extent 2500.0 0.693205 0.060914 0.4680 0.658900 0.71305 0.740225 0.8296
Roundness 2500.0 0.791533 0.055924 0.5546 0.751900 0.79775 0.834325 0.9396
Aspect_Ration 2500.0 2.041702 0.315997 1.1487 1.801050 1.98420 2.262075 3.1444
Compactness 2500.0 0.704121 0.053067 0.5608 0.663475 0.70770 0.743500 0.9049
Patrząc na tabelę, porównując oznaczać i odchylenie standardowe (std
) widać, że większość cech ma średnią daleką od odchylenia standardowego. Oznacza to, że wartości danych nie są skoncentrowane wokół wartości średniej, ale bardziej rozproszone wokół niej – innymi słowy, mają duża zmienność.
Również, patrząc na minimum (min
) i maksymalny (max
) kolumny, niektóre funkcje, takie jak Area
, Convex_Area
, mają duże różnice między wartościami minimalnymi i maksymalnymi. Oznacza to, że te kolumny zawierają bardzo małe dane, a także bardzo duże wartości danych lub wyższa amplituda między wartościami danych.
Przy dużej zmienności, wysokiej amplitudzie i cechach z różnymi jednostkami miary, większość naszych danych skorzystałaby z posiadania tej samej skali dla wszystkich cech lub bycia łuskowaty. Skalowanie danych wycentruje dane wokół średniej i zmniejszy jej wariancję.
Ten scenariusz prawdopodobnie wskazuje również, że w danych występują wartości odstające i ekstremalne. Więc najlepiej mieć trochę leczenie odstające oprócz skalowania danych.
Istnieje kilka algorytmów uczenia maszynowego, na przykład algorytmy oparte na drzewie, takie jak Klasyfikacja lasów losowych, na które nie ma wpływu duża wariancja danych, wartości odstające i wartości ekstremalne. Regresja logistyczna jest inny, opiera się na funkcji, która kategoryzuje nasze wartości, a na parametry tej funkcji mogą mieć wpływ wartości, które są poza ogólnym trendem danych i mają dużą wariancję.
Więcej o regresji logistycznej zrozumiemy za chwilę, gdy ją zaimplementujemy. Na razie możemy dalej eksplorować nasze dane.
Uwaga: W informatyce jest popularne powiedzenie: „Śmieci wchodzą, śmieci wychodzą” (GIGO), który doskonale nadaje się do uczenia maszynowego. Oznacza to, że gdy mamy śmieciowe dane – pomiary, które same w sobie nie opisują zjawiska, dane, które nie zostały zrozumiane i dobrze przygotowane zgodnie z rodzajem algorytmu lub modelu, prawdopodobnie wygenerują niepoprawny wynik, który nie będzie działał na na co dzień.
To jeden z powodów, dla których tak ważne jest badanie, rozumienie danych i sposób działania wybranego modelu. Robiąc to, możemy uniknąć umieszczania śmieci w naszym modelu – zamiast tego nadawać mu wartość i uzyskiwać wartość.
Wizualizacja danych
Do tej pory, dzięki statystykom opisowym, mamy nieco abstrakcyjną migawkę niektórych cech danych. Kolejnym ważnym krokiem jest wizualizacja tego i potwierdzenie naszej hipotezy o wysokiej wariancji, amplitudzie i wartościach odstających. Aby sprawdzić, czy to, co zaobserwowaliśmy do tej pory, pokazuje dane, możemy wykreślić kilka wykresów.
Interesujące jest również zobaczyć, jak cechy odnoszą się do dwóch przewidywanych klas. Aby to zrobić, zaimportujmy seaborn
spakuj i użyj pairplot
wykres, aby przyjrzeć się rozkładowi każdej cechy i każdej separacji klas na cechę:
import seaborn as sns
sns.pairplot(data=df, hue='Class')
Uwaga: Wykonanie powyższego kodu może trochę potrwać, ponieważ para wykresów łączy wykresy rozrzutu wszystkich funkcji (może), a także wyświetla rozkłady funkcji.
Patrząc na wykres pary, widzimy, że w większości przypadków punkty Çerçevelik
klasy są wyraźnie oddzielone od punktów Ürgüp Sivrisi
klasa. Albo punkty jednej klasy są po prawej stronie, podczas gdy inne są po lewej, albo niektóre są na górze, a inne na dole. Gdybyśmy mieli użyć jakiejś krzywej lub linii do rozdzielenia klas, to pokazuje, że łatwiej je rozdzielić, gdyby były pomieszane, klasyfikacja byłaby trudniejszym zadaniem.
W Eccentricity
, Compactness
i Aspect_Ration
kolumny, niektóre punkty, które są „izolowane” lub odbiegające od ogólnego trendu danych – wartości odstające – są również łatwo dostrzegalne.
Patrząc na przekątną od lewego górnego rogu do prawego dolnego rogu wykresu, zauważ, że rozkłady danych są również oznaczone kolorami zgodnie z naszymi klasami. Kształty rozkładu i odległość między obiema krzywymi to kolejne wskaźniki ich rozdzielności – im dalej od siebie, tym lepiej. W większości przypadków nie nakładają się one na siebie, co oznacza, że łatwiej je rozdzielić, również przyczyniając się do naszego zadania.
W kolejności możemy również wykreślić wykresy pudełkowe wszystkich zmiennych za pomocą sns.boxplot()
metoda. W większości przypadków pomocne jest zorientowanie wykresów pudełkowych poziomo, więc kształty wykresów pudełkowych są takie same jak kształty rozkładu, możemy to zrobić za pomocą orient
Argument:
sns.boxplot(data=df, orient='h')
Na powyższej fabule zauważ, że Area
i Convex_Area
mają tak dużą wartość w porównaniu z wartościami innych kolumn, że zgniatają inne wykresy pudełkowe. Aby móc spojrzeć na wszystkie wykresy pudełkowe, możemy przeskalować cechy i ponownie je wykreślić.
Zanim to zrobimy, po prostu zrozummy, że jeśli istnieją wartości cech, które są ściśle powiązane z innymi wartościami, na przykład – jeśli są wartości, które również rosną, gdy inne wartości cech są większe, posiadanie dodatnia korelacja; lub jeśli istnieją wartości, które działają odwrotnie, zmniejszają się, podczas gdy inne wartości maleją, mając a ujemną korelację.
Jest to ważne, ponieważ silne relacje w danych mogą oznaczać, że niektóre kolumny pochodzą z innych kolumn lub mają podobne znaczenie do naszego modelu. Kiedy tak się dzieje, wyniki modelu mogą być przeszacowane i chcemy wyników bliższych rzeczywistości. Jeśli istnieją silne korelacje, oznacza to również, że możemy zmniejszyć liczbę cech i użyć mniejszej liczby kolumn, dzięki czemu model będzie bardziej oszczędny.
Uwaga: Domyślna korelacja obliczona z corr()
metoda to Współczynnik korelacji Pearsona. Współczynnik ten jest wskazany, gdy dane są ilościowe, mają rozkład normalny, nie mają wartości odstających i mają zależność liniową.
Innym wyborem byłoby obliczenie Współczynnik korelacji Spearmana. Współczynnik Spearmana jest używany, gdy dane są porządkowe, nieliniowe, mają dowolny rozkład i mają wartości odstające. Zauważ, że nasze dane nie w pełni pasują do założeń Pearsona czy Spearmana (jest też więcej metod korelacji, takich jak Kendall). Ponieważ nasze dane są ilościowe i ważne jest, abyśmy zmierzyli ich liniową zależność, użyjemy współczynnika Pearsona.
Przyjrzyjmy się korelacjom między zmiennymi, a następnie możemy przejść do wstępnego przetwarzania danych. Obliczymy korelacje z corr()
metody i wizualizuj je za pomocą Seaborn's heatmap()
. Standardowy rozmiar mapy termicznej jest zwykle mały, więc zaimportujemy matplotlib
(ogólny silnik wizualizacji/biblioteka, na której zbudowany jest Seaborn) i zmień rozmiar za pomocą figsize
:
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 10))
correlations = df.corr()
sns.heatmap(correlations, annot=True)
W tej mapie cieplnej wartości bliższe 1 lub -1 to wartości, na które musimy zwrócić uwagę. Pierwszy przypadek oznacza wysoką korelację dodatnią, a drugi wysoką korelację ujemną. Obie wartości, jeśli nie powyżej 0.8 lub -0.8 będą korzystne dla naszego modelu regresji logistycznej.
Gdy istnieją wysokie korelacje, takie jak ta z 0.99
pomiędzy Aspec_Ration
i Compactness
, oznacza to, że możemy wybrać tylko użycie Aspec_Ration
lub tylko Compactness
, zamiast obu z nich (ponieważ byłyby prawie równe predyktory siebie). To samo dotyczy Eccentricity
i Compactness
z -0.98
korelacja, dla Area
i Perimeter
z 0.94
korelacja i kilka innych kolumn.
Wstępne przetwarzanie danych
Ponieważ już od jakiegoś czasu eksplorujemy dane, możemy rozpocząć ich wstępne przetwarzanie. Na razie użyjmy wszystkich funkcji przewidywania klas. Po uzyskaniu pierwszego modelu, linii bazowej, możemy usunąć niektóre z silnie skorelowanych kolumn i porównać je z linią bazową.
Kolumny funkcji będą nasze X
dane i kolumna klasy, nasz y
dane docelowe:
y = df['Class']
X = df.drop(columns=['Class'], axis=1)
Przekształcanie cech kategorialnych w cechy numeryczne
Jeśli chodzi o nasze Class
kolumna – jej wartości nie są liczbami, co oznacza, że również musimy je przekształcić. Istnieje wiele sposobów na dokonanie tej transformacji; tutaj użyjemy replace()
metoda i wymień Çerçevelik
do 0
i Ürgüp Sivrisi
do 1
.
y = y.replace('Çerçevelik', 0).replace('Ürgüp Sivrisi', 1)
Pamiętaj o mapowaniu! Czytając wyniki ze swojego modelu, będziesz chciał przekonwertować je z powrotem przynajmniej w swoim umyśle lub z powrotem na nazwę klasy dla innych użytkowników.
Podział danych na pociągi i zestawy testowe
Podczas naszej eksploracji zauważyliśmy, że funkcje wymagają skalowania. Gdybyśmy zrobili skalowanie teraz lub w sposób automatyczny, skalowalibyśmy wartości z całym X
i y
. W takim przypadku przedstawimy wyciek danych, ponieważ wartości przyszłego zestawu testowego miałyby wpływ na skalowanie. Wyciek danych jest częstą przyczyną nieodtwarzalnych wyników i złudnej wysokiej wydajności modeli ML.
Myślenie o skalowaniu pokazuje, że najpierw musimy podzielić X
i y
dane dalej do pociągów i zestawów testowych, a następnie do dopasować skaler na zestawie treningowym oraz do przekształcać zarówno zestaw testowy, jak i testowy (bez wpływu zestawu testowego na skaler, który to robi). W tym celu użyjemy Scikit-Learn train_test_split()
metoda:
from sklearn.model_selection import train_test_split
SEED = 42
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=.25,
random_state=SEED)
Oprawa test_size=.25
zapewnia, że wykorzystujemy 25% danych do testowania i 75% do szkolenia. Można to pominąć, gdy jest to domyślny podział, ale Pythona sposób pisania kodu informuje, że bycie „wyraźnym jest lepsze niż niejawne”.
Uwaga: Zdanie „wyraźne jest lepsze niż ukryte” jest odniesieniem do: Zen Pythonalub PEP20. Zawiera kilka sugestii dotyczących pisania kodu w Pythonie. Jeśli te sugestie są przestrzegane, kod jest brany pod uwagę Pythona. Możesz dowiedzieć się o tym więcej tutaj.
Po podzieleniu danych na zestawy treningowe i testowe, dobrą praktyką jest sprawdzenie, ile rekordów znajduje się w każdym zestawie. Można to zrobić za pomocą shape
atrybut:
X_train.shape, X_test.shape, y_train.shape, y_test.shape
Wyświetla:
((1875, 12), (625, 12), (1875,), (625,))
Widzimy, że po podziale mamy 1875 rekordów do treningu i 625 do testów.
Skalowanie danych
Gdy mamy gotowy zestaw pociągów i testów, możemy przystąpić do skalowania danych za pomocą Scikit-Learn StandardScaler
obiekt (lub inne skalery dostarczone przez bibliotekę). Aby uniknąć wycieków, skaler jest przymocowany do X_train
dane i wartości pociągu są następnie wykorzystywane do skalowania – lub przekształcania – zarówno danych pociągu, jak i danych testowych:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
Ponieważ zazwyczaj dzwonisz:
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
Pierwsze dwie linie można zwinąć za pomocą liczby pojedynczej fit_transform()
wezwanie, które dopasowuje skaler do zestawu i przekształca go za jednym zamachem. Możemy teraz odtworzyć wykresy pudełkowe, aby zobaczyć różnicę po przeskalowaniu danych.
Biorąc pod uwagę, że skalowanie usuwa nazwy kolumn, przed wykreśleniem możemy ponownie uporządkować dane pociągu w ramkę danych z nazwami kolumn, aby ułatwić wizualizację:
column_names = df.columns[:12]
X_train = pd.DataFrame(X_train, columns=column_names)
sns.boxplot(data=X_train, orient='h')
W końcu możemy zobaczyć wszystkie nasze boxploty! Zauważ, że wszystkie z nich mają wartości odstające i cechy, które przedstawiają rozkład dalej od normalnego (które mają krzywe pochylone w lewo lub w prawo), takie jak Solidity
, Extent
, Aspect_Ration
, Compactedness
, są takie same, które miały wyższe korelacje.
Usuwanie wartości odstających za pomocą metody IQR
Wiemy już, że na regresję logistyczną mogą wpływać wartości odstające. Jednym ze sposobów ich leczenia jest zastosowanie metody zwanej Zakres międzykwartylowy or IQR. Pierwszym krokiem metody IQR jest podzielenie naszych danych o pociągach na cztery części, zwane kwartylami. pierwszy kwartyl, Q1, wynosi 25% danych, drugi, Q2, do 50%, trzecia, Q3, do 75%, a ostatnia, Q4, do 100%. Pudełka na wykresie pudełkowym są definiowane metodą IQR i stanowią jej wizualną reprezentację.
W przypadku poziomego wykresu pudełkowego pionowa linia po lewej stronie oznacza 25% danych, pionowa linia pośrodku — 50% danych (lub mediana), a ostatnia pionowa linia po prawej — 75% danych . Im bardziej równomierny rozmiar mają oba kwadraty określone przez pionowe linie – lub im bardziej pionowa linia środkowa znajduje się pośrodku – oznacza, że nasze dane są bliższe rozkładowi normalnemu lub mniej przekrzywione, co jest pomocne w naszej analizie.
Poza skrzynką IQR, po obu jej stronach znajdują się również poziome linie. Linie te oznaczają minimalne i maksymalne wartości rozkładu określone przez
$$
Minimum = Q1 – 1.5*IQR
$$
i
$$
Maksimum = Q3 + 1.5*IQR
$$
IQR to dokładnie różnica między Q3 a Q1 (lub Q3 – Q1) i jest to najbardziej centralny punkt danych. Dlatego podczas znajdowania IQR kończymy filtrowaniem wartości odstających w krańcach danych lub w punktach minimum i maksimum. Wykresy pudełkowe dają nam zapowiedź tego, jaki będzie wynik metody IQR.
Możemy użyć Pand quantile()
metoda znajdowania naszych kwantyli i iqr
z scipy.stats
pakiet do uzyskania międzykwartylowego zakresu danych dla każdej kolumny:
from scipy.stats import iqr
Q1 = X_train.quantile(q=.25)
Q3 = X_train.quantile(q=.75)
IQR = X_train.apply(iqr)
Teraz mamy Q1, Q3 i IQR, możemy odfiltrować wartości bliższe mediany:
minimum = X_train < (Q1-1.5*IQR)
maximum = X_train > (Q3+1.5*IQR)
filter = ~(minimum | maximum).any(axis=1)
X_train = X_train[filter]
Po przefiltrowaniu naszych wierszy treningowych możemy zobaczyć, ile z nich nadal znajduje się w danych z shape
:
X_train.shape
To skutkuje:
(1714, 12)
Widzimy, że po przefiltrowaniu liczba wierszy wzrosła z 1875 do 1714. Oznacza to, że 161 wierszy zawierało wartości odstające lub 8.5% danych.
Uwaga: Zaleca się, aby filtrowanie wartości odstających, usuwanie wartości NaN i inne czynności związane z filtrowaniem i czyszczeniem danych pozostawały poniżej lub do 10% danych. Spróbuj pomyśleć o innych rozwiązaniach, jeśli filtrowanie lub usuwanie przekracza 10% danych.
Po usunięciu wartości odstających jesteśmy prawie gotowi do włączenia danych do modelu. Do dopasowania modelu użyjemy danych pociągu. X_train
jest filtrowany, ale co z y_train
?
y_train.shape
To daje:
(1875,)
Zauważ, że y_train
nadal ma 1875 wierszy. Musimy dopasować liczbę y_train
wierszy do liczby X_train
wiersze, a nie tylko arbitralnie. Musimy usunąć wartości y wystąpień usuniętych nasion dyni, które prawdopodobnie są rozproszone po całym y_train
ustawić. Filtrowane X_train
nadal ma swoje oryginalne indeksy, a indeks zawiera luki, w których usunęliśmy wartości odstające! Możemy wtedy użyć indeksu X_train
DataFrame do wyszukiwania odpowiednich wartości w y_train
:
y_train = y_train.iloc[X_train.index]
Po wykonaniu tej czynności możemy spojrzeć na y_train
ukształtuj ponownie:
y_train.shape
Które wyjścia:
(1714,)
Zapoznaj się z naszym praktycznym, praktycznym przewodnikiem dotyczącym nauki Git, zawierającym najlepsze praktyki, standardy przyjęte w branży i dołączoną ściągawkę. Zatrzymaj polecenia Google Git, a właściwie uczyć się to!
Teraz, y_train
również ma 1714 rzędów i są takie same jak X_train
wydziwianie. Jesteśmy wreszcie gotowi do stworzenia naszego modelu regresji logistycznej!
Implementacja modelu regresji logistycznej
Najtrudniejsza część została wykonana! Wstępne przetwarzanie jest zwykle trudniejsze niż tworzenie modeli, jeśli chodzi o korzystanie z bibliotek takich jak Scikit-Learn, które usprawniły stosowanie modeli ML do zaledwie kilku wierszy.
Najpierw importujemy LogisticRegression
klasy i tworzyć jej instancję, tworząc a LogisticRegression
obiekt:
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression(random_state=SEED)
Po drugie, dopasowujemy nasze dane pociągów do logreg
model z fit()
metody i przewidzieć nasze dane testowe za pomocą predict()
metody, przechowywanie wyników jako y_pred
:
logreg.fit(X_train.values, y_train)
y_pred = logreg.predict(X_test)
Przewidywaliśmy już z naszym modelem! Spójrzmy na pierwsze 3 rzędy w X_train
aby zobaczyć, jakie dane wykorzystaliśmy:
X_train[:3]
Powyższy kod daje:
Area Perimeter Major_Axis_Length Minor_Axis_Length Convex_Area Equiv_Diameter Eccentricity Solidity Extent Roundness Aspect_Ration Compactness
0 -1.098308 -0.936518 -0.607941 -1.132551 -1.082768 -1.122359 0.458911 -1.078259 0.562847 -0.176041 0.236617 -0.360134
1 -0.501526 -0.468936 -0.387303 -0.376176 -0.507652 -0.475015 0.125764 0.258195 0.211703 0.094213 -0.122270 0.019480
2 0.012372 -0.209168 -0.354107 0.465095 0.003871 0.054384 -0.453911 0.432515 0.794735 0.647084 -0.617427 0.571137
A przy pierwszych 3 prognozach w y_pred
aby zobaczyć wyniki:
y_pred[:3]
To skutkuje:
array([0, 0, 0])
Dla tych trzech rzędów nasze przewidywania były takie, że były to nasiona pierwszej klasy, Çerçevelik
.
Z regresja logistyczna, zamiast przewidywać klasę końcową, taką jak 0
, możemy również przewidzieć prawdopodobieństwo, że wiersz odnosi się do 0
klasa. Tak się dzieje, gdy regresja logistyczna klasyfikuje dane, a predict()
Metoda następnie przekazuje tę prognozę przez próg, aby zwrócić „twardą” klasę. Aby przewidzieć prawdopodobieństwo przynależności do klasy, predict_proba()
jest używany:
y_pred_proba = logreg.predict_proba(X_test)
Przyjrzyjmy się również pierwszym 3 wartościom przewidywań prawdopodobieństw y:
y_pred_proba[:3]
Które wyjścia:
# class 0 class 1
array([[0.54726628, 0.45273372],
[0.56324527, 0.43675473],
[0.86233349, 0.13766651]])
Teraz zamiast trzech zer mamy jedną kolumnę dla każdej klasy. W kolumnie po lewej, zaczynając od 0.54726628
, są prawdopodobieństwami danych odnoszących się do klasy 0
; i w prawej kolumnie, zaczynając od 0.45273372
, czy prawdopodobieństwo tego dotyczy klasy? 1
.
Uwaga: Ta różnica w klasyfikacji jest również znana jako ciężko i miękki Prognoza. Twarde przewidywanie grupuje przewidywania w klasę, podczas gdy miękkie przewidywania wyświetlają prawdopodobieństwo instancji należącej do klasy.
Jest więcej informacji o tym, jak sporządzono przewidywany wynik. Właściwie to nie było 0
, ale 55% szans na klasę 0
, i 45% szans na klasę 1
. To pokazuje, jak pierwsze trzy X_test
punkty danych, dotyczące klasy 0
, są naprawdę jasne tylko w odniesieniu do trzeciego punktu danych, z prawdopodobieństwem 86% – i nie tak bardzo w przypadku pierwszych dwóch punktów danych.
Podczas komunikowania wyników za pomocą metod ML – zazwyczaj najlepiej jest zwrócić miękką klasę i związane z nią prawdopodobieństwo jako "zaufanie" tej klasyfikacji.
Porozmawiamy więcej o tym, jak to jest obliczane, gdy zagłębimy się w model. W tym momencie możemy przejść do kolejnego kroku.
Ocena modelu za pomocą raportów klasyfikacyjnych
Trzecim krokiem jest sprawdzenie, jak model działa na danych testowych. Możemy zaimportować Scikit-Learn classification_report()
i przekazać nasze y_test
i y_pred
jako argumenty. Następnie możemy wydrukować jego odpowiedź.
Raport klasyfikacji zawiera najczęściej używane metryki klasyfikacji, takie jak precyzja, odwołanie, wynik f1, precyzja.
- Detaliczność: aby zrozumieć, jakie prawidłowe wartości przewidywania zostały uznane za prawidłowe przez nasz klasyfikator. Precyzja podzieli te prawdziwe wartości dodatnie przez wszystkie przewidywane wartości dodatnie:
$$
precyzja = frac{tekst{prawdziwe dodatnie}}{tekst{prawdziwe dodatnie} + tekst{fałszywie dodatnie}}
$$
- Odwołanie: aby zrozumieć, ile prawdziwych pozytywów zostało zidentyfikowanych przez nasz klasyfikator. Przypomnienie oblicza się, dzieląc prawdziwe pozytywy przez wszystko, co należało przewidzieć jako pozytywne:
$$
przypomnienie = frac{tekst{prawdziwe pozytywne}}{tekst{prawdziwe pozytywne} + tekst{fałszywe negatywne}}
$$
- Wynik F1: czy zrównoważony lub Średnia harmoniczna precyzji i przypomnienia. Najniższa wartość to 0, a najwyższa to 1. Kiedy
f1-score
jest równy 1, oznacza to, że wszystkie klasy zostały poprawnie przewidziane – jest to bardzo trudny wynik do uzyskania na rzeczywistych danych:
$$
tekst{f1-score} = 2* frac{tekst{precyzja} * tekst{przypomnij}}{tekst{precyzja} + tekst{przypomnij}}
$$
- Dokładność: opisuje, ile przewidywań nasz klasyfikator wykonał poprawnie. Najniższa wartość dokładności wynosi 0, a najwyższa 1. Ta wartość jest zwykle mnożona przez 100, aby uzyskać wartość procentową:
$$
dokładność = frac{tekst{liczba poprawnych prognoz}}{tekst{całkowita liczba prognoz}}
$$
Uwaga: Niezwykle trudno jest uzyskać 100% dokładność na jakichkolwiek rzeczywistych danych, jeśli tak się stanie, należy mieć świadomość, że może nastąpić jakiś wyciek lub coś złego – nie ma zgody co do idealnej wartości dokładności i jest to również zależne od kontekstu. Wartość 70%, co oznacza, że klasyfikator popełni błędy na 30% danych lub powyżej 70% zwykle wystarcza dla większości modeli.
from sklearn.metrics import classification_report
cr = classification_report(y_test, y_pred)
print(cr)
Następnie możemy spojrzeć na wynik raportu klasyfikacji:
precision recall f1-score support
0 0.83 0.91 0.87 316
1 0.90 0.81 0.85 309
accuracy 0.86 625
macro avg 0.86 0.86 0.86 625
weighted avg 0.86 0.86 0.86 625
To jest nasz wynik. Zauważ, że precision
, recall
, f1-score
, accuracy
wszystkie wskaźniki są bardzo wysokie, powyżej 80%, co jest idealnym rozwiązaniem – ale na te wyniki prawdopodobnie miały wpływ wysokie korelacje i nie utrzymają się na dłuższą metę.
Dokładność modelu wynosi 86%, co oznacza, że w 14% przypadków niepoprawna klasyfikacja. Mamy te ogólne informacje, ale ciekawie byłoby wiedzieć, czy 14% błędów ma miejsce w odniesieniu do klasyfikacji klasy 0
lub klasa 1
. Aby zidentyfikować, które klasy są błędnie zidentyfikowane jako które iz jaką częstotliwością – możemy obliczyć i wykreślić a matryca zamieszania przewidywań naszego modelu.
Ocena modelu za pomocą matrycy pomyłek
Obliczmy, a następnie wykreślmy macierz pomyłek. Po wykonaniu tej czynności możemy zrozumieć każdą jej część. Aby wykreślić macierz pomyłek, użyjemy Scikit-Learn confusion_matrix()
, które zaimportujemy z metrics
moduł.
Matryca pomyłek jest łatwiejsza do wizualizacji za pomocą Seaborn heatmap()
. Tak więc, po wygenerowaniu, przekażemy naszą macierz pomyłek jako argument dla heatmapy:
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d')
- Macierz zamieszania: macierz pokazuje, ile próbek model dał się dobrze lub źle dla każdej klasy. Wartości, które były poprawne i poprawnie przewidziane, nazywane są prawdziwe pozytywy, a te, które zostały uznane za pozytywne, ale nie były pozytywne, to fałszywe alarmy. Ta sama nomenklatura prawdziwe negatywy i fałszywe negatywy służy do wartości ujemnych;
Patrząc na wykres macierzy pomyłek, widzimy, że mamy 287
wartości, które były 0
i przewidziano jako 0
- lub prawdziwe pozytywy dla klasy 0
(nasiona Çerçevelik). Mamy też 250
prawdziwe pozytywy dla klasy 1
(Nasiona Ürgüp Sivrisi). Prawdziwe pozytywy zawsze znajdują się na przekątnej matrycy, która biegnie od lewego górnego rogu do prawego dolnego.
Mamy też 29
wartości, które miały być 0
, ale przewidziano jako 1
(fałszywe alarmy) i 59
wartości, które były 1
i przewidziano jako 0
(fałszywe negatywy). Dzięki tym liczbom możemy zrozumieć, że błąd, który model popełnia najbardziej, polega na przewidywaniu wyników fałszywie ujemnych. Tak więc, w większości przypadków może to skończyć się klasyfikacją nasion Ürgüp Sivrisi jako nasion Çerçevelik.
Ten rodzaj błędu tłumaczy się również 81% odwołaniem klasy 1
. Zauważ, że metryki są połączone. A różnica w wycofaniu wynika z posiadania o 100 mniej próbek klasy Ürgüp Sivrisi. Jest to jeden z implikacji posiadania tylko kilku próbek mniej niż w przypadku drugiej klasy. Aby jeszcze bardziej poprawić zapamiętywanie, możesz poeksperymentować z wagami klas lub użyć większej liczby próbek Ürgüp Sivrisi.
Do tej pory wykonaliśmy większość tradycyjnych kroków związanych z nauką o danych i wykorzystaliśmy model regresji logistycznej jako czarną skrzynkę.
Uwaga: Jeśli chcesz iść dalej, użyj Walidacja krzyżowa (CV) i przeszukiwanie siatki poszukać odpowiednio modelu, który najbardziej uogólnia dane, oraz najlepszych parametrów modelu, które są wybierane przed uczeniem, lub hiperparametry.
Idealnie, dzięki CV i Grid Search, można również zaimplementować połączony sposób wykonywania kroków wstępnego przetwarzania danych, dzielenia danych, modelowania i oceny – co jest łatwe dzięki Scikit-Learn rurociągi.
Teraz nadszedł czas, aby otworzyć czarną skrzynkę i zajrzeć do niej, aby głębiej zrozumieć, jak działa regresja logistyczna.
Zagłębianie się w to, jak naprawdę działa regresja logistyczna
Połączenia regresja słowo nie jest tam przypadkowo, aby zrozumieć, co robi regresja logistyczna, możemy przypomnieć sobie, co jej rodzeństwo, regresja liniowa robi z danymi. Formuła regresji liniowej była następująca:
$$
y = b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldotki + b_n * x_n
$$
W którym b0 był punkt przecięcia regresji, b1 współczynnik i x1 dane.
To równanie dało linię prostą, która była używana do przewidywania nowych wartości. Przypominając wstęp, różnica polega na tym, że nie będziemy przewidywać nowych wartości, ale klasę. Więc ta prosta linia musi się zmienić. W regresji logistycznej wprowadzamy nieliniowość, a prognoza jest teraz wykonywana za pomocą krzywej zamiast linii:
Zauważ, że podczas gdy linia regresji liniowej trwa i składa się z ciągłych nieskończonych wartości, krzywa regresji logistycznej może być podzielona pośrodku i ma ekstrema w wartościach 0 i 1. Ten kształt „S” jest powodem, dla którego klasyfikuje dane – punkty znajdujące się bliżej lub leżące na najwyższym końcu należą do klasy 1, podczas gdy punkty znajdujące się w dolnym kwadrancie lub bliżej 0 należą do klasy 0. Środek „S” to środek między 0 a 1, 0.5 – jest to próg dla punktów regresji logistycznej.
Rozumiemy już wizualną różnicę między regresją logistyczną a liniową, ale co z formułą? Wzór na regresję logistyczną jest następujący:
$$
y = b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldotki + b_n * x_n
$$
Można go również zapisać jako:
$$
y_{prob} = ułamek{1}{1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}
$$
Lub nawet być napisany jako:
$$
y_{prob} = frac{e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}{1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + kropki + b_n * x_n)}}
$$
W powyższym równaniu mamy prawdopodobieństwo wejścia zamiast jego wartości. Ma 1 jako licznik, więc może dać wartość z przedziału od 0 do 1, a 1 plus wartość w mianowniku, więc jego wartość wynosi 1 i coś – oznacza to, że cały wynik ułamka nie może być większy niż 1 .
A jaka jest wartość w mianowniku? To jest e, podstawa logarytmu naturalnego (około 2.718282), podniesiona do potęgi regresji liniowej:
$$
e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldotki + b_n * x_n)}
$$
Innym sposobem napisania byłoby:
$$
ln lewo(frac{p}{1-p} prawo) = {(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}
$$
W tym ostatnim równaniu ln jest logarytmem naturalnym (podstawa e) i p jest prawdopodobieństwem, więc logarytm prawdopodobieństwa wyniku jest taki sam, jak wynik regresji liniowej.
Innymi słowy, mając wynik regresji liniowej i logarytm naturalny, możemy uzyskać prawdopodobieństwo, że dane wejściowe odnoszą się lub nie do zaprojektowanej klasy.
Cały proces wyprowadzania regresji logistycznej wygląda następująco:
$$
p{X} = ułamek{e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}{1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + kropki + b_n * x_n)}}
$$
$$
p(1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldotków + b_n * x_n)}) = e^{(b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldotków + b_n * x_n)}
$$
$$
p + p*e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldotki + b_n * x_n)} = e^{(b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldotki + b_n * x_n)}
$$
p
=
e
(
b
0
+
b
1
∗
x
1
+
b
2
∗
x
2
+
b
3
∗
x
3
+
...
+
b
n
∗
x
n
)
-
p
∗
e
(
b
0
+
b
1
∗
x
1
+
b
2
∗
x
2
+
b
3
∗
x
3
+
...
+
b
n
∗
x
n
)
$$
frac{p}{1-p} = e^{(b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldots + b_n * x_n)}
$$
$$
ln lewo( frac{p}{1-p} prawo) = (b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldots + b_n * x_n)
$$
Oznacza to, że model regresji logistycznej ma również współczynniki i wartość przecięcia. Ponieważ używa regresji liniowej i dodaje do niej nieliniowy składnik z logarytmem naturalnym (e
).
Możemy zobaczyć wartości współczynników i punkt przecięcia naszego modelu, tak samo jak w przypadku regresji liniowej, używając coef_
i intercept_
nieruchomości:
logreg.coef_
Który wyświetla współczynniki każdej z 12 funkcji:
array([[ 1.43726172, -1.03136968, 0.24099522, -0.61180768, 1.36538261,
-1.45321951, -1.22826034, 0.98766966, 0.0438686 , -0.78687889,
1.9601197 , -1.77226097]])
logreg.intercept_
Skutkuje to:
array([0.08735782])
Dzięki współczynnikom i wartościom przecięcia możemy obliczyć przewidywane prawdopodobieństwa naszych danych. Chodźmy pierwszy X_test
wartości ponownie, jako przykład:
X_test[:1]
Zwraca pierwszy wiersz X_test
jako tablica NumPy:
array([[-1.09830823, -0.93651823, -0.60794138, -1.13255059, -1.0827684 ,
-1.12235877, 0.45891056, -1.07825898, 0.56284738, -0.17604099,
0.23661678, -0.36013424]])
Zgodnie z początkowym równaniem:
$$
p{X} = ułamek{e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}{1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + kropki + b_n * x_n)}}
$$
W Pythonie mamy:
import math
lin_reg = logreg.intercept_[0] +
((logreg.coef_[0][0]* X_test[:1][0][0])+
(logreg.coef_[0][1]* X_test[:1][0][1])+
(logreg.coef_[0][2]* X_test[:1][0][2])+
(logreg.coef_[0][3]* X_test[:1][0][3])+
(logreg.coef_[0][4]* X_test[:1][0][4])+
(logreg.coef_[0][5]* X_test[:1][0][5])+
(logreg.coef_[0][6]* X_test[:1][0][6])+
(logreg.coef_[0][7]* X_test[:1][0][7])+
(logreg.coef_[0][8]* X_test[:1][0][8])+
(logreg.coef_[0][9]* X_test[:1][0][9])+
(logreg.coef_[0][10]* X_test[:1][0][10])+
(logreg.coef_[0][11]* X_test[:1][0][11]))
px = math.exp(lin_reg)/(1 +(math.exp(lin_reg)))
px
To skutkuje:
0.45273372469369133
Jeśli ponownie spojrzymy na predict_proba
wynik pierwszego X_test
linia, mamy:
logreg.predict_proba(X_test[:1])
Oznacza to, że oryginalne równanie regresji logistycznej daje nam prawdopodobieństwo danych wejściowych dotyczących klasy 1
, aby dowiedzieć się, które prawdopodobieństwo dotyczy klasy 0
, możemy po prostu:
1 - px
Zauważ, że oba px
i 1-px
są identyczne z predict_proba
wyniki. W ten sposób obliczana jest regresja logistyczna i dlaczego regresja jest częścią jego nazwy. Ale co z terminem? logistyka?
Termin logistyka pochodzi z logit, czyli funkcję, którą już widzieliśmy:
$$
Po lewej (frac{p}{1-p} po prawej)
$$
Właśnie to obliczyliśmy za pomocą px
i 1-px
. To jest logit, zwany także logarytmiczne kursy ponieważ jest równy logarytmowi szans gdzie p
jest prawdopodobieństwem.
Wnioski
W tym przewodniku omówiliśmy jeden z najbardziej podstawowych algorytmów klasyfikacji uczenia maszynowego, tj. regresja logistyczna.
Początkowo zaimplementowaliśmy regresję logistyczną jako czarną skrzynkę z biblioteką uczenia maszynowego Scikit-Learn, a później zrozumieliśmy to krok po kroku, aby mieć jasne, dlaczego i skąd pochodzą terminy regresja i logistyka.
Zbadaliśmy również i przeanalizowaliśmy dane, rozumiejąc, że jest to jedna z najważniejszych części analizy naukowej danych.
Stąd radzę się pobawić wieloklasowa regresja logistyczna, regresja logistyczna dla więcej niż dwóch klas — można zastosować ten sam algorytm regresji logistycznej do innych zestawów danych, które mają wiele klas, i zinterpretować wyniki.
Uwaga: Dostępny jest dobry zbiór zbiorów danych tutaj do zabawy.
Radziłbym również zapoznać się z L1 i L2 regularyzacje, są sposobem na „karanie” wyższych danych, aby zbliżyły się do normy, utrzymując złożoność modelu, dzięki czemu algorytm może uzyskać lepszy wynik. Użyta przez nas implementacja Scikit-Learn ma już domyślnie regularyzację L2. Kolejną rzeczą, na którą należy zwrócić uwagę, jest różnica solwery, Takie jak lbgs
, które optymalizują wydajność algorytmu regresji logistycznej.
Ważne jest również, aby przyjrzeć się statystyczny podejście do regresji logistycznej. To ma Założenia o zachowaniu danych oraz o innych statystykach, które muszą być przechowywane, aby zagwarantować zadowalające wyniki, takie jak:
- obserwacje są niezależne;
- nie ma wielokoliniowości między zmiennymi objaśniającymi;
- nie ma skrajnych wartości odstających;
- istnieje liniowa zależność między zmiennymi objaśniającymi a logitem zmiennej odpowiedzi;
- wielkość próbki jest wystarczająco duża.
Zwróć uwagę, ile z tych założeń zostało już uwzględnionych w naszej analizie i przetwarzaniu danych.
Mam nadzieję, że nadal będziesz badać, co regresja logistyczna ma do zaoferowania we wszystkich jej różnych podejściach!