Definitiv guide till logistisk regression i Python PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Definitiv guide till logistisk regression i Python

Beskrivning

Ibland förvirrad med linjär regression av nybörjare – på grund av att de delar termen regression - logistisk återgång är mycket annorlunda än linjär regression. Medan linjär regression förutsäger värden som 2, 2.45, 6.77 eller kontinuerliga värden, vilket gör det till en regression algoritm, logistisk återgång förutsäger värden som 0 eller 1, 1 eller 2 eller 3, som är diskreta värden, vilket gör det till en klassificering algoritm. Ja, den heter regression men är en klassificering algoritm. Mer om det om ett ögonblick.

Därför, om ditt datavetenskapliga problem involverar kontinuerliga värden, kan du tillämpa en regression algoritm (linjär regression är en av dem). Annars, om det involverar klassificering av indata, diskreta värden eller klasser, kan du använda en klassificering algoritm (logistisk regression är en av dem).

I den här guiden kommer vi att utföra logistisk regression i Python med Scikit-Learn-biblioteket. Vi kommer också att förklara varför ordet "regression" finns i namnet och hur logistisk regression fungerar.

För att göra det kommer vi först att ladda data som kommer att klassificeras, visualiseras och förbehandlas. Sedan kommer vi att bygga en logistisk regressionsmodell som kommer att förstå dessa data. Denna modell kommer sedan att utvärderas och användas för att förutsäga värden baserat på ny input.

Motivation

Företaget du arbetar för gjorde ett samarbete med en turkisk jordbruksgård. Detta partnerskap innebär försäljning av pumpafrön. Pumpafrön är mycket viktiga för mänsklig näring. De innehåller en bra andel kolhydrater, fett, protein, kalcium, kalium, fosfor, magnesium, järn och zink.

I datavetenskapsteamet är din uppgift att se skillnaden mellan typerna av pumpafrön bara genom att använda data – eller klassificerande uppgifterna enligt frötyp.

Den turkiska gården arbetar med två pumpafrösorter, den ena heter Çerçevelik och den andra Ürgüp Sivrisi.

För att klassificera pumpafröna har ditt team följt 2021-tidningen "Användningen av maskininlärningsmetoder för klassificering av pumpafrön (Cucurbita pepo L.). Genetiska resurser och växtutveckling” från Koklu, Sarigil och Ozbek – i detta dokument finns det en metod för att fotografera och extrahera frönsmått från bilderna.

Efter att ha slutfört processen som beskrivs i uppsatsen extraherades följande mätningar:

  • Area – antalet pixlar inom gränserna för ett pumpafrö
  • Omkrets – omkretsen i pixlar av ett pumpafrö
  • Huvudaxelns längd – även omkretsen i pixlar av ett pumpafrö
  • Längd på mindre axel – det lilla axelavståndet för ett pumpafrö
  • Excentricitet – excentriciteten hos ett pumpafrö
  • Konvext område – antalet pixlar i det minsta konvexa skalet i området som bildas av pumpafrön
  • Utsträckning – förhållandet mellan en pumpafröarea och begränsningsrutans pixlar
  • Ekvivalent Diameter – kvadratroten av multiplikationen av arean av pumpafrön med fyra dividerat med pi
  • kompakthet – andelen av pumpafrönas yta i förhållande till cirkelns yta med samma omkrets
  • soliditet – pumpafrönas konvexa och konvexa tillstånd
  • rundhet – pumpafrönens ovalitet utan att beakta dess snedvridningar i kanterna
  • Aspect Ratio – bildförhållandet för pumpafröna

Det är de måtten du måste arbeta med. Förutom måtten finns det också Klass etikett för de två typerna av pumpafrön.

För att börja klassificera fröna, låt oss importera data och börja titta på dem.

Förstå datamängden

Notera: Du kan ladda ner pumpadatasetet här..

Efter att ha laddat ner datauppsättningen kan vi ladda den till en dataramstruktur med hjälp av pandas bibliotek. Eftersom det är en excel-fil kommer vi att använda read_excel() metod:

import pandas as pd

fpath = 'dataset/pumpkin_seeds_dataset.xlsx' 
df = pd.read_excel(fpath)

När data har laddats in kan vi ta en snabb titt på de första 5 raderna med hjälp av head() metod:

df.head() 

Detta resulterar i:

	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

Här har vi alla mått i sina respektive kolumner, vår pass, och också Klass kolumn, vår mål, som är den sista i dataramen. Vi kan se hur många mätningar vi har med hjälp av shape attribut:

df.shape 

Utgången är:

(2500, 13)

Formresultatet berättar att det finns 2500 poster (eller rader) i datasetet och 13 kolumner. Eftersom vi vet att det finns en målkolumn betyder det att vi har 12 funktionskolumner.

Vi kan nu utforska målvariabeln, pumpafröna Class. Eftersom vi kommer att förutsäga den variabeln är det intressant att se hur många prover av varje pumpafrö vi har. Ju mindre skillnaden är mellan antalet instanser i våra klasser, desto mer balanserat är vårt urval och desto bättre är våra förutsägelser.

Denna inspektion kan göras genom att räkna varje fröprov med value_counts() metod:

df['Class'].value_counts() 

Ovanstående kod visar:

Çerçevelik       1300
Ürgüp Sivrisi    1200
Name: Class, dtype: int64

Vi kan se att det finns 1300 prover av Çerçevelik frö och 1200 prover av Ürgüp Sivrisi utsäde. Observera att skillnaden mellan dem är 100 prover, en mycket liten skillnad, vilket är bra för oss och indikerar att det inte finns något behov av att balansera om antalet prover.

Låt oss också titta på den beskrivande statistiken för våra funktioner med describe() metod för att se hur väl distribuerad data är. Vi kommer också att överföra den resulterande tabellen med T för att göra det lättare att jämföra statistik:

df.describe().T

Den resulterande tabellen är:

					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

Genom att titta på tabellen, när man jämför betyda och standardavvikelse (std) kolumner, kan man se att de flesta funktioner har ett medelvärde som är långt ifrån standardavvikelsen. Det indikerar att datavärdena inte är koncentrerade kring medelvärdet, utan mer spridda runt det – med andra ord, de har hög variation.

Även när man tittar på minsta (min) Och maximal (max) kolumner, vissa funktioner, som t.ex Areaoch Convex_Area, har stora skillnader mellan minimi- och maxvärden. Detta betyder att dessa kolumner har mycket små data och även mycket stora datavärden, eller högre amplitud mellan datavärden.

Med hög variabilitet, hög amplitud och funktioner med olika måttenheter skulle de flesta av våra data tjäna på att ha samma skala för alla funktioner eller vara skalad. Dataskalning kommer att centrera data kring medelvärdet och minska dess varians.

Detta scenario indikerar troligen också att det finns extremvärden och extremvärden i data. Så det är bäst att ha några avvikande behandling förutom att skala data.

Det finns vissa maskininlärningsalgoritmer, till exempel trädbaserade algoritmer som t.ex Slumpmässig skogsklassificering, som inte påverkas av hög datavarians, extremvärden och extremvärden. Logistisk återgång är annorlunda, den är baserad på en funktion som kategoriserar våra värden, och parametrarna för den funktionen kan påverkas av värden som ligger utanför den allmänna datatrenden och har hög varians.

Vi kommer att förstå mer om logistisk regression om lite när vi får implementera det. Tills vidare kan vi fortsätta utforska vår data.

Notera: Det finns ett populärt talesätt inom datavetenskap: "Skräp in, skräp ut" (GIGO), som är väl lämpad för maskininlärning. Detta betyder att när vi har skräpdata – mätningar som inte beskriver fenomenet i sig själva, kommer data som inte var förstått och väl förberedd enligt typen av algoritm eller modell, sannolikt att generera en felaktig utdata som inte fungerar på från dag till dag.
Detta är en av anledningarna till att det är så viktigt att utforska, förstå data och hur den valda modellen fungerar. Genom att göra det kan vi undvika att lägga skräp i vår modell – lägga värde i det istället och få ut värde.

Visualisera data

Hittills har vi, med den beskrivande statistiken, en något abstrakt ögonblicksbild av vissa egenskaper hos datan. Ett annat viktigt steg är att visualisera det och bekräfta vår hypotes om hög varians, amplitud och extremvärden. För att se om det vi har observerat hittills syns i datan kan vi rita några grafer.

Det är också intressant att se hur funktionerna förhåller sig till de två klasserna som kommer att förutsägas. För att göra det, låt oss importera seaborn paketera och använda pairplot graf för att titta på varje funktionsfördelning och varje klassseparation per funktion:

import seaborn as sns


sns.pairplot(data=df, hue='Class')

Notera: Ovanstående kod kan ta ett tag att köra, eftersom pardiagrammet kombinerar scatterplots av alla funktioner (det kan), och även visar funktionsdistributionerna.

När vi tittar på pardiagrammet kan vi se att i de flesta fall punkterna i Çerçevelik klass är tydligt åtskilda från punkterna i Ürgüp Sivrisi klass. Antingen är poängen för en klass till höger när de andra är till vänster, eller så är vissa upp medan de andra är nere. Om vi ​​skulle använda någon form av kurva eller linje för att separera klasser visar detta att det är lättare att separera dem, om de var blandade skulle klassificering vara en svårare uppgift.

I Eccentricity, Compactness och Aspect_Ration kolumner, vissa punkter som är "isolerade" eller avviker från den allmänna datatrenden - extremvärden - är också lätt att upptäcka.

När du tittar på diagonalen från det övre vänstra till det nedre högra hörnet i diagrammet, lägg märke till att datafördelningarna också är färgkodade enligt våra klasser. Fördelningsformerna och avståndet mellan de båda kurvorna är andra indikatorer på hur separerbara de är – ju längre från varandra, desto bättre. I de flesta fall är de inte överlagrade, vilket innebär att de är lättare att separera, vilket också bidrar till vår uppgift.

I sekvens kan vi också plotta boxplotterna för alla variabler med sns.boxplot() metod. Oftast är det bra att orientera boxplotterna horisontellt, så formerna på boxplotterna är desamma som distributionsformerna, vi kan göra det med orient argument:


sns.boxplot(data=df, orient='h') 

Definitiv guide till logistisk regression i Python PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Lägg märke till det i handlingen ovan Area och Convex_Area har en så hög magnitud jämfört med storleken på de andra kolumnerna att de klämmer ihop de andra boxplotterna. För att kunna titta på alla boxplots kan vi skala funktionerna och plotta dem igen.

Innan vi gör det, låt oss bara förstå att om det finns värden för funktioner som är intimt relaterade till andra värden, till exempel – om det finns värden som också blir större när andra funktionsvärden blir större, har en positiv korrelation; eller om det finns värden som gör det motsatta, blir mindre medan andra värden blir mindre, med a Negativ korrelation.

Detta är viktigt att titta på eftersom att ha starka relationer i data kan innebära att vissa kolumner härrör från andra kolumner eller har en liknande betydelse som vår modell. När det händer kan modellresultaten överskattas och vi vill ha resultat som ligger närmare verkligheten. Om det finns starka korrelationer betyder det också att vi kan minska antalet funktioner och använda färre kolumner vilket gör modellen mer snål.

Notera: Standardkorrelationen beräknad med corr() metoden är Pearson korrelationskoefficient. Denna koefficient indikeras när data är kvantitativ, normalfördelad, inte har extremvärden och har ett linjärt samband.

Ett annat val skulle vara att beräkna Spearmans korrelationskoefficient. Spearmans koefficient används när data är ordinal, icke-linjär, har någon fördelning och har extremvärden. Lägg märke till att våra data inte helt passar in i Pearsons eller Spearmans antaganden (det finns också fler korrelationsmetoder, som Kendalls). Eftersom vår data är kvantitativ och det är viktigt för oss att mäta dess linjära samband kommer vi att använda Pearsons koefficient.

Låt oss ta en titt på korrelationerna mellan variabler och sedan kan vi gå vidare till förbearbetning av data. Vi kommer att beräkna korrelationerna med corr() metod och visualisera dem med Seaborn's heatmap(). Värmekartans standardstorlek tenderar att vara liten, så vi kommer att importera matplotlib (generell visualiseringsmotor/bibliotek som Seaborn är byggd ovanpå) och ändra storleken med figsize:

import matplotlib.pyplot as plt
plt.figure(figsize=(15, 10))

correlations = df.corr()
sns.heatmap(correlations, annot=True) 

Definitiv guide till logistisk regression i Python PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

I denna värmekarta är värdena närmare 1 eller -1 de värden vi måste vara uppmärksamma på. Det första fallet anger en hög positiv korrelation och det andra en hög negativ korrelation. Båda värdena, om inte över 0.8 eller -0.8 kommer att vara fördelaktiga för vår logistiska regressionsmodell.

När det finns höga korrelationer som den av 0.99 mellan Aspec_Ration och Compactness, detta betyder att vi kan välja att endast använda Aspec_Ration eller bara Compactness, istället för båda (eftersom de nästan är lika prediktorer av varandra). Detsamma gäller för Eccentricity och Compactness med en -0.98 korrelation, för Area och Perimeter med en 0.94 korrelation och några andra kolumner.

Förbearbetning av data

Eftersom vi redan har utforskat data ett tag kan vi börja förbearbeta det. Låt oss för närvarande använda alla funktioner för klassförutsägelsen. Efter att ha erhållit en första modell, en baslinje, kan vi sedan ta bort några av de högkorrelerade kolumnerna och jämföra den med baslinjen.

Funktionskolumnerna kommer att vara vår X data och klasskolumnen, vår y måldata:

y = df['Class']
X = df.drop(columns=['Class'], axis=1)

Förvandla kategoriska funktioner till numeriska funktioner

Angående vår Class kolumn – dess värden är inte siffror, det betyder att vi också måste omvandla dem. Det finns många sätt att göra denna transformation; här kommer vi att använda replace() metod och ersätt Çerçevelik till 0 och Ürgüp Sivrisi till 1.

y = y.replace('Çerçevelik', 0).replace('Ürgüp Sivrisi', 1)

Ha kartläggningen i åtanke! När du läser resultat från din modell, vill du konvertera dessa tillbaka åtminstone i ditt sinne, eller tillbaka till klassnamnet för andra användare.

Dela upp data i tåg- och testuppsättningar

I vår utforskning har vi noterat att funktionerna behövde skalas. Om vi ​​gjorde skalningen nu, eller på ett automatiskt sätt, skulle vi skala värden med hela X och y. I så fall skulle vi presentera dataläcka, eftersom värdena för det snart att vara testset skulle ha påverkat skalningen. Dataläckage är en vanlig orsak till irreproducerbara resultat och illusoriskt höga prestanda hos ML-modeller.

Att tänka på skalningen visar att vi först måste dela X och y data vidare in i tåg- och testset och sedan till passa en scaler på träningssetet, och till transformera både tåget och testset (utan att testsetet någonsin påverkar skalaren som gör detta). För detta kommer vi att använda Scikit-Learn's train_test_split() metod:

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)

Att lägga plattor test_size=.25 ser till att vi använder 25 % av data för testning och 75 % för utbildning. Detta kan utelämnas när det väl är standarduppdelningen, men Pythonisk sätt att skriva kod ger råd om att vara "explicit är bättre än implicit".

Notera: Meningen "explicit är bättre än implicit" är en hänvisning till Zen av Python, eller PEP20. Den innehåller några förslag för att skriva Python-kod. Om dessa förslag följs övervägs koden Pythonisk. Du kan veta mer om det här..

Efter att ha delat upp data i tåg- och testuppsättningar är det en god praxis att titta på hur många poster som finns i varje uppsättning. Det kan man göra med shape attribut:

X_train.shape, X_test.shape, y_train.shape, y_test.shape

Detta visar:

((1875, 12), (625, 12), (1875,), (625,))

Vi kan se att efter splittringen har vi 1875 rekord för träning och 625 för testning.

Skala data

När vi har våra tåg- och testset redo kan vi fortsätta att skala data med Scikit-Learn StandardScaler objekt (eller andra skalare som tillhandahålls av biblioteket). För att undvika läckage är scalern monterad på X_train data och tågvärden används sedan för att skala – eller transformera – både tåg- och testdata:

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

Eftersom du vanligtvis ringer:

scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)

De två första raderna kan kollapsas med en singular fit_transform() call, som passar scalern på setet, och förvandlar den på en gång. Vi kan nu reproducera boxplot-graferna för att se skillnaden efter skalning av data.

Med tanke på att skalningen tar bort kolumnnamn kan vi innan plottning organisera tågdata i en dataram med kolumnnamn igen för att underlätta visualiseringen:

column_names = df.columns[:12] 
X_train = pd.DataFrame(X_train, columns=column_names)

sns.boxplot(data=X_train, orient='h')

Definitiv guide till logistisk regression i Python PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Äntligen kan vi se alla våra boxplotter! Lägg märke till att alla av dem har extremvärden, och de funktioner som presenterar en fördelning längre från det normala (som har kurvor antingen sneda åt vänster eller höger), som t.ex. Solidity, Extent, Aspect_Rationoch Compactedness, är samma som hade högre korrelationer.

Ta bort extremvärden med IQR-metoden

Vi vet redan att logistisk regression kan påverkas av extremvärden. Ett av sätten att behandla dem är att använda en metod som kallas Kvartilavståndet or I.Q.R.. Det första steget i IQR-metoden är att dela upp våra tågdata i fyra delar, så kallade kvartiler. Den första kvartilen, Q1, uppgår till 25 % av data, den andra, Q2, till 50 %, den tredje, Q3, till 75 %, och den sista, Q4, till 100 %. Rutorna i boxplotten definieras av IQR-metoden och är en visuell representation av den.

Med tanke på en horisontell boxplot markerar den vertikala linjen till vänster 25 % av data, den vertikala linjen i mitten, 50 % av data (eller medianen) och den sista vertikala linjen till höger, 75 % av data . Ju jämnare i storlek båda kvadraterna som definieras av de vertikala linjerna är – eller ju mer den vertikala medianlinjen är i mitten – betyder att våra data är närmare normalfördelningen eller mindre skeva, vilket är till hjälp för vår analys.

Förutom IQR-rutan finns det även horisontella linjer på båda sidor av den. Dessa linjer markerar de lägsta och maximala fördelningsvärdena som definieras av

$$
Minimum = Q1 – 1.5*IQR
$$

och

$$
Max = Q3 + 1.5*IQR
$$

IQR är exakt skillnaden mellan Q3 och Q1 (eller Q3 – Q1) och det är den mest centrala punkten för data. Det är därför vi när vi hittar IQR, slutar med att vi filtrerar extremiteterna i dataextremiteterna, eller i minimum- och maximumpoäng. Boxplots ger oss en tjuvtitt på vad resultatet av IQR-metoden blir.

Definitiv guide till logistisk regression i Python PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Vi kan använda pandor quantile() metod för att hitta våra kvantiler, och iqr från scipy.stats paket för att få det interkvartila dataintervallet för varje kolumn:

from scipy.stats import iqr

Q1 = X_train.quantile(q=.25)
Q3 = X_train.quantile(q=.75)

IQR = X_train.apply(iqr)

Nu har vi Q1, Q3 och IQR, vi kan filtrera bort värdena närmare medianen:


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]

Efter att ha filtrerat våra träningsrader kan vi se hur många av dem som fortfarande finns i data med shape:

X_train.shape

Detta resulterar i:

(1714, 12)

Vi kan se att antalet rader gick från 1875 till 1714 efter filtrering. Det betyder att 161 rader innehöll extremvärden eller 8.5 % av data.

Notera: Det rekommenderas att filtrering av extremvärden, borttagning av NaN-värden och andra åtgärder som involverar filtrering och rensning av data förblir under eller upp till 10 % av data. Försök att tänka på andra lösningar om din filtrering eller borttagning överstiger 10 % av din data.

Efter att ha tagit bort extremvärden är vi nästan redo att inkludera data i modellen. För modellanpassningen kommer vi att använda tågdata. X_train är filtrerad, men hur är det y_train?

y_train.shape

Denna utgångar:

(1875,)

Lägg märke till att y_train har fortfarande 1875 rader. Vi måste matcha antalet y_train rader till antalet X_train rader och inte bara godtyckligt. Vi måste ta bort y-värdena för de förekomster av pumpafrön som vi tog bort, som sannolikt är utspridda genom y_train uppsättning. Den filtrerade X_train stil har sina ursprungliga index och indexet har luckor där vi tagit bort extremvärden! Vi kan sedan använda indexet för X_train DataFrame för att söka efter motsvarande värden i y_train:

y_train = y_train.iloc[X_train.index]

Efter att ha gjort det kan vi titta på y_train form igen:

y_train.shape

Vilka utgångar:

(1714,)

Kolla in vår praktiska, praktiska guide för att lära dig Git, med bästa praxis, branschaccepterade standarder och medföljande fuskblad. Sluta googla Git-kommandon och faktiskt lära Det!

Nu, y_train har också 1714 rader och de är samma som X_train rader. Vi är äntligen redo att skapa vår logistiska regressionsmodell!

Implementering av den logistiska regressionsmodellen

Det svåra är gjort! Förbearbetning är vanligtvis svårare än modellutveckling, när det kommer till att använda bibliotek som Scikit-Learn, som har effektiviserat tillämpningen av ML-modeller till bara ett par rader.

Först importerar vi LogisticRegression klass och instansiera den, skapa en LogisticRegression objekt:

from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression(random_state=SEED)

För det andra anpassar vi våra tågdata till logreg modell med fit() metod och förutsäga våra testdata med predict() metod, lagra resultaten som y_pred:



logreg.fit(X_train.values, y_train)
y_pred = logreg.predict(X_test)

Vi har redan gjort förutsägelser med vår modell! Låt oss titta på de första 3 raderna i X_train för att se vilken data vi har använt:

X_train[:3]

Koden ovan ger ut:

       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

Och vid de första 3 förutsägelserna i y_pred för att se resultatet:

y_pred[:3] 

Detta resulterar i:

array([0, 0, 0])

För de tre raderna var våra förutsägelser att de var frön av den första klassen, Çerçevelik.

Med logistisk återgång, istället för att förutsäga slutklassen, som t.ex 0, kan vi också förutsäga sannolikheten att raden hör till 0 klass. Detta är vad som faktiskt händer när logistisk regression klassificerar data och predict() Metoden skickar sedan denna förutsägelse genom en tröskel för att returnera en "hård" klass. För att förutsäga sannolikheten att tillhöra en klass, predict_proba() är använd:

y_pred_proba = logreg.predict_proba(X_test)

Låt oss också ta en titt på de tre första värdena av y-sannolikhetsförutsägelserna:

y_pred_proba[:3] 

Vilka utgångar:

        # class 0   class 1   
array([[0.54726628, 0.45273372],
       [0.56324527, 0.43675473],
       [0.86233349, 0.13766651]])

Nu, istället för tre nollor, har vi en kolumn för varje klass. I kolumnen till vänster börjar med 0.54726628, är sannolikheterna för data som hör till klassen 0; och i den högra kolumnen, börjar med 0.45273372, är sannolikheten att det hör till klassen 1.

Notera: Denna skillnad i klassificering kallas också hård och mjuk förutsägelse. Hård förutsägelse placerar förutsägelsen i en klass, medan mjuka förutsägelser matar ut Sannolikheten av den instans som tillhör en klass.

Det finns mer information om hur den förutspådda produktionen gjordes. Det var det faktiskt inte 0, men en 55% chans till klass 0, och en 45% chans till klass 1. Detta visar hur de tre första X_test datapunkter, som hänför sig till klass 0, är verkligen tydliga bara när det gäller den tredje datapunkten, med en sannolikhet på 86 % – och inte så mycket för de två första datapunkterna.

När du kommunicerar fynd med hjälp av ML-metoder – är det vanligtvis bäst att returnera en mjuk klass och den tillhörande sannolikheten som "förtroende" av den klassificeringen.

Vi kommer att prata mer om hur det beräknas när vi går djupare in i modellen. Vid det här laget kan vi gå vidare till nästa steg.

Utvärdering av modellen med klassificeringsrapporter

Det tredje steget är att se hur modellen presterar på testdata. Vi kan importera Scikit-Learn classification_report() och passera vår y_test och y_pred som argument. Efter det kan vi skriva ut svaret.

Klassificeringsrapporten innehåller de mest använda klassificeringsmåtten, som t.ex precision, minns, f1-poängoch noggrannhet.

  1. Precision: för att förstå vilka korrekta prediktionsvärden som ansågs vara korrekta av vår klassificerare. Precision kommer att dela dessa sanna positiva värden med allt som förutspåddes som ett positivt:

$$
precision = frac{text{true positive}}{text{true positive} + text{false positive}}
$$

  1. Recall: för att förstå hur många av de sanna positiva som identifierades av vår klassificerare. Återkallelsen beräknas genom att dividera de sanna positiva med allt som borde ha förutspåtts som positivt:

$$
recall = frac{text{true positive}}{text{true positive} + text{false negative}}
$$

  1. F1-poäng: är den balanserade eller harmoniskt medelvärde av precision och återkallelse. Det lägsta värdet är 0 och det högsta är 1. När f1-score är lika med 1, betyder det att alla klasser förutspåddes korrekt – det här är en mycket svår poäng att få med riktiga data:

$$
text{f1-score} = 2* frac{text{precision} * text{recall}}{text{precision} + text{recall}}
$$

  1. Noggrannhet: beskriver hur många förutsägelser vår klassificerare fick rätt. Det lägsta noggrannhetsvärdet är 0 och det högsta är 1. Det värdet multipliceras vanligtvis med 100 för att få en procentsats:

$$
precision = frac{text{antal korrekta förutsägelser}}{text{totalt antal förutsägelser}}
$$

Notera: Det är extremt svårt att få 100 % noggrannhet på någon riktig data, om det händer, var medveten om att något läckage eller något fel kan hända – det finns ingen konsensus om ett idealiskt noggrannhetsvärde och det är också kontextberoende. Ett värde på 70 %, vilket innebär att klassificeraren kommer att göra misstag på 30 % av data, eller över 70 % tenderar att vara tillräckligt för de flesta modeller.

from sklearn.metrics import classification_report
cr = classification_report(y_test, y_pred)
print(cr)

Vi kan sedan titta på klassificeringsrapportens utdata:

				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

Detta är vårt resultat. Lägg märke till att precision, recall, f1-scoreoch accuracy alla mätvärden är mycket höga, över 80 %, vilket är idealiskt – men dessa resultat påverkades förmodligen av höga korrelationer och kommer inte att hålla i det långa loppet.

Modellens noggrannhet är 86%, vilket innebär att den får klassificeringen fel 14% av gångerna. Vi har den övergripande informationen, men det skulle vara intressant att veta om de 14 % misstagen sker när det gäller klassificeringen av klass 0 eller klass 1. För att identifiera vilka klasser som är felidentifierade som vilka och i vilken frekvens – kan vi beräkna och plotta en förvirringsmatris av vår modells förutsägelser.

Utvärdera modellen med en förvirringsmatris

Låt oss beräkna och rita sedan upp förvirringsmatrisen. Efter att ha gjort det kan vi förstå varje del av det. För att rita upp förvirringsmatrisen använder vi Scikit-Learn confusion_matrix(), som vi importerar från metrics modul.

Förvirringsmatrisen är lättare att visualisera med en Seaborn heatmap(). Så efter att ha genererat den kommer vi att skicka vår förvirringsmatris som ett argument för värmekartan:

from sklearn.metrics import confusion_matrix

cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d')

Definitiv guide till logistisk regression i Python PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

  1. Förvirringsmatris: matrisen visar hur många prover modellen fick rätt eller fel för varje klass. De värden som var korrekta och korrekt förutsagda kallas sanna positiva, och de som förutspåddes som positiva men inte var positiva kallas falska positiva. Samma nomenklatur för verkliga negativa och falska negativ används för negativa värden;

Genom att titta på förvirringsmatrisplotten kan vi se att vi har 287 värderingar som var 0 och förutspådde som 0 - eller sanna positiva för klassen 0 (Cerçevelik-fröna). Vi har också 250 riktigt positiva saker för klassen 1 (Ürgüp Sivrisi frön). De sanna positiva finns alltid i matrisdiagonalen som går från övre vänstra till nedre högra.

Vi har också 29 värderingar som skulle vara 0, men förutspått som 1 (falska positiva) Och 59 värderingar som var 1 och förutspådde som 0 (falska negativ). Med de siffrorna kan vi förstå att det fel som modellen gör mest är att den förutsäger falska negativ. Så det kan för det mesta sluta med att ett Ürgüp Sivrisi-frö klassificeras som ett Çerçevelik-frö.

Denna typ av fel förklaras också av 81% återkallande av klass 1. Observera att mätvärdena är anslutna. Och skillnaden i återkallelsen kommer från att ha 100 färre prover av Ürgüp Sivrisi-klassen. Detta är en av konsekvenserna av att bara ha några få prover mindre än den andra klassen. För att ytterligare förbättra återkallelsen kan du antingen experimentera med klassvikter eller använda fler Ürgüp Sivrisi-prover.

Hittills har vi utfört de flesta av de traditionella datavetenskapliga stegen och använt den logistiska regressionsmodellen som en svart låda.

Notera: Om du vill gå längre, använd Korsvalidering (CV) och Grid Search att leta efter den modell som generaliserar mest gällande data respektive de bästa modellparametrarna som väljs innan träning, eller hyperparametrar.

Helst skulle du med CV och Grid Search också kunna implementera ett sammanlänkade sätt att göra dataförbehandlingssteg, datadelning, modellering och utvärdering – vilket är enkelt med Scikit-Learn rörledningar.

Nu är det dags att öppna den svarta lådan och titta in i den, för att gå djupare in i förståelsen av hur logistisk regression fungerar.

Går djupare in på hur logistisk regression verkligen fungerar

Smakämnen regression ordet är inte där av en slump, för att förstå vad logistisk regression gör kan vi komma ihåg vad dess syskon, linjär regression gör med data. Den linjära regressionsformeln var följande:

$$
y = b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n
$$

I vilken b0 var regressionsavlyssningen, b1 koefficienten och x1 uppgifterna.

Den ekvationen resulterade i en rak linje som användes för att förutsäga nya värden. Med tanke på inledningen är skillnaden nu att vi inte kommer att förutsäga nya värden, utan en klass. Så den raka linjen måste ändras. Med logistisk regression introducerar vi en icke-linjäritet och förutsägelsen görs nu med en kurva istället för en linje:

Definitiv guide till logistisk regression i Python PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Observera att medan den linjära regressionslinjen fortsätter och är gjord av kontinuerliga oändliga värden, kan den logistiska regressionskurvan delas i mitten och har extremvärden i 0 och 1 värden. Den "S"-formen är anledningen till att den klassificerar data – de punkter som är närmare eller faller på den högsta extremiteten tillhör klass 1, medan de punkter som är i den nedre kvadranten eller närmare 0, tillhör klass 0. Mitten av "S" är mitten mellan 0 och 1, 0.5 - det är tröskeln för de logistiska regressionspunkterna.

Definitiv guide till logistisk regression i Python PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

Vi förstår redan den visuella skillnaden mellan logistisk och linjär regression, men hur är det med formeln? Formeln för logistisk regression är följande:

$$
y = b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n
$$

Det kan också skrivas som:

$$
y_{prob} = frac{1}{1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}
$$

Eller till och med skrivas som:

$$
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)_n x_n x_n
$$

I ekvationen ovan har vi sannolikheten för input istället för dess värde. Den har 1 som täljare så den kan resultera i ett värde mellan 0 och 1, och 1 plus ett värde i sin nämnare, så att dess värde är 1 och något – detta betyder att hela bråkresultatet inte kan vara större än 1 .

Och vad är värdet som finns i nämnaren? Det är e, basen för den naturliga logaritmen (ungefär 2.718282), upphöjd till styrkan av linjär regression:

$$
e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}
$$

Ett annat sätt att skriva det skulle vara:

$$
ln left( frac{p}{1-p} höger) = {(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}
$$

I den sista ekvationen, ln är den naturliga logaritmen (bas e) och p är sannolikheten, så logaritmen för sannolikheten för resultatet är densamma som det linjära regressionsresultatet.

Med andra ord, med det linjära regressionsresultatet och den naturliga logaritmen, kan vi komma fram till sannolikheten för att en indata tillhör eller inte en designad klass.

Hela logistiska regressionshärledningsprocessen är följande:

$$
p{X} = 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 + ldots + b)}}
$$

$$
p(1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}) = e^{(b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldots + b)_}n * x_
$$

$$
p + p*e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)} = e^{(b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldots + 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 left( frac{p}{1-p} höger) = (b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldots + b_n * x_n)
$$

Detta innebär att den logistiska regressionsmodellen också har koefficienter och ett interceptvärde. Eftersom den använder en linjär regression och lägger till en icke-linjär komponent till den med den naturliga logaritmen (e).

Vi kan se värdena för koefficienterna och skärningen av vår modell, på samma sätt som vi gjorde för linjär regression, med hjälp av coef_ och intercept_ egenskaper:

logreg.coef_

Som visar koefficienterna för var och en av de 12 funktionerna:

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_

Det resulterar i:

array([0.08735782])

Med koefficienterna och skärningsvärdena kan vi beräkna de förutspådda sannolikheterna för våra data. Låt oss ta det första X_test värden igen, som ett exempel:

X_test[:1]

Detta returnerar den första raden av X_test som en NumPy-array:

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]])

Efter den initiala ekvationen:

$$
p{X} = 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 + ldots + b)}}
$$

I python har vi:

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

Detta resulterar i:

0.45273372469369133

Om vi ​​tittar igen på predict_proba resultatet av den första X_test linje, vi har:

logreg.predict_proba(X_test[:1])


Detta betyder att den ursprungliga logistiska regressionsekvationen ger oss sannolikheten för inmatningen avseende klass 1, för att ta reda på vilken sannolikhet som är för klass 0, vi kan helt enkelt:

1 - px


Lägg märke till att båda px och 1-px är identiska med predict_proba resultat. Det är så logistisk regression beräknas och varför regression är en del av dess namn. Men hur är det med termen logistisk?

Uttrycket logistisk kommer från logit, vilket är en funktion som vi redan har sett:

$$
ln left (frac{p}{1-p} höger)
$$

Vi har precis räknat ut det med px och 1-px. Detta är logit, även kallat log-odds eftersom det är lika med logaritmen för oddsen där p är en sannolikhet.

Slutsats

I den här guiden har vi studerat en av de mest grundläggande maskininlärningsklassificeringsalgoritmerna, dvs logistisk återgång.

Till en början implementerade vi logistisk regression som en svart låda med Scikit-Learns maskininlärningsbibliotek, och senare förstod vi det steg för steg för att ha ett tydligt varför och var begreppen regression och logistik kommer ifrån.

Vi har också utforskat och studerat data, och förstår att det är en av de mest avgörande delarna av en datavetenskaplig analys.

Härifrån skulle jag råda dig att leka med multiklass logistisk regression, logistisk regression för mer än två klasser – du kan använda samma logistiska regressionsalgoritm för andra datamängder som har flera klasser och tolka resultaten.

Notera: En bra samling datauppsättningar finns tillgänglig här. för dig att leka med.

Jag skulle också råda dig att studera L1 och L2 legaliseringar, de är ett sätt att "bestraffa" högre data för att den ska bli närmare det normala, vilket håller ut modellens komplexitet, så att algoritmen kan få ett bättre resultat. Scikit-Learn-implementeringen vi använde har redan L2-regularisering som standard. En annan sak att titta på är det annorlunda lösare, Såsom lbgs, som optimerar den logistiska regressionsalgoritmens prestanda.

Det är också viktigt att ta en titt på statistisk inställning till logistisk regression. Det har antaganden om datas beteende och om annan statistik som måste hållas för att garantera tillfredsställande resultat, såsom:

  • observationerna är oberoende;
  • det finns ingen multikollinearitet bland förklarande variabler;
  • det finns inga extrema extremvärden;
  • det finns ett linjärt samband mellan förklarande variabler och logit för svarsvariabeln;
  • urvalsstorleken är tillräckligt stor.

Lägg märke till hur många av dessa antaganden som redan täcktes i vår analys och behandling av data.

Jag hoppas att du fortsätter att utforska vad logistisk regression har att erbjuda i alla dess olika tillvägagångssätt!

Tidsstämpel:

Mer från Stackabuse