Introductie
Soms verward met lineaire regressie door beginners - vanwege het delen van de term regressie - logistische regressie is heel anders dan lineaire regressie. Terwijl lineaire regressie waarden voorspelt zoals 2, 2.45, 6.77 of continue waarden, waardoor het een regressie algoritme, logistische regressie voorspelt waarden zoals 0 of 1, 1 of 2 of 3, die zijn discrete waarden, waardoor het een classificatie algoritme. Ja, het heet regressie maar is een classificatie algoritme. Daarover straks meer.
Als uw datawetenschapsprobleem continue waarden betreft, kunt u daarom een regressie algoritme (lineaire regressie is er een van). Anders, als het gaat om het classificeren van invoer, discrete waarden of klassen, kunt u een classificatie algoritme (logistieke regressie is er een van).
In deze handleiding voeren we logistische regressie uit in Python met de Scikit-Learn-bibliotheek. We zullen ook uitleggen waarom het woord "regressie" aanwezig is in de naam en hoe logistische regressie werkt.
Om dat te doen, zullen we eerst gegevens laden die worden geclassificeerd, gevisualiseerd en voorverwerkt. Vervolgens zullen we een logistisch regressiemodel bouwen dat die gegevens zal begrijpen. Dit model zal vervolgens worden geรซvalueerd en gebruikt om waarden te voorspellen op basis van nieuwe invoer.
Motivatie
Het bedrijf waarvoor u werkt heeft een samenwerking met een Turkse landbouwboerderij. Deze samenwerking omvat de verkoop van pompoenpitten. Pompoenpitten zijn erg belangrijk voor de menselijke voeding. Ze bevatten een goede verhouding koolhydraten, vetten, eiwitten, calcium, kalium, fosfor, magnesium, ijzer en zink.
In het data science-team is het jouw taak om het verschil te zien tussen de soorten pompoenpitten, gewoon door gegevens te gebruiken โ of classificeren de gegevens per zaadtype.
De Turkse boerderij werkt met twee soorten pompoenpitten, de ene heet erรงevelik en de andere rgรผp Sivrisi.
Om de pompoenpitten te classificeren, heeft uw team de 2021-paper gevolgd โHet gebruik van machine learning-methoden bij de classificatie van pompoenpitten (Cucurbita pepo L.). Genetische bronnen en gewasevolutieโ van Koklu, Sarigil en Ozbek - in dit artikel is er een methode voor het fotograferen en extraheren van de zaadmetingen uit de afbeeldingen.
Na het voltooien van het proces dat in het document wordt beschreven, werden de volgende metingen geรซxtraheerd:
- De Omgeving โ het aantal pixels binnen de randen van een pompoenpit
- Omtrek โ de omtrek in pixels van een pompoenpit
- Lengte hoofdas โ ook de omtrek in pixels van een pompoenpit
- Kleine aslengte โ de kleine asafstand van een pompoenzaad
- Excentriciteit โ de excentriciteit van een pompoenpit
- Convex gebied โ het aantal pixels van de kleinste bolle schil in het gebied gevormd door het pompoenzaad
- Omvang โ de verhouding van een pompoenpitgebied tot de pixels van de begrenzingsbox
- Equivalente diameter โ de vierkantswortel van de vermenigvuldiging van de oppervlakte van de pompoenpit met vier gedeeld door pi
- Compactheid โ de verhouding van de oppervlakte van de pompoenpit ten opzichte van de oppervlakte van de cirkel met dezelfde omtrek
- Stevigheid โ de bolle en bolle toestand van de pompoenpitten
- Rondheid โ de ovaliteit van pompoenpitten zonder rekening te houden met de vervormingen van de randen
- Aspect Ratio โ de beeldverhouding van de pompoenpitten
Dat zijn de maten waar je mee aan de slag moet. Naast de afmetingen is er ook de Klasse label voor de twee soorten pompoenpitten.
Laten we, om te beginnen met het classificeren van de zaden, de gegevens importeren en ernaar gaan kijken.
De gegevensset begrijpen
Opmerking: Je kunt de pompoendataset downloaden hier.
Na het downloaden van de dataset kunnen we deze in een dataframestructuur laden met behulp van de pandas
bibliotheek. Omdat het een Excel-bestand is, gebruiken we de read_excel()
methode:
import pandas as pd
fpath = 'dataset/pumpkin_seeds_dataset.xlsx'
df = pd.read_excel(fpath)
Zodra de gegevens zijn geladen, kunnen we een snelle blik werpen op de eerste 5 rijen met behulp van de head()
methode:
df.head()
Dit resulteert in:
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
Hier hebben we alle metingen in hun respectievelijke kolommen, onze functionaliteiten, en ook de Klasse kolom, onze doel, de laatste in het dataframe. We kunnen zien hoeveel metingen we hebben met behulp van de shape
attribuut:
df.shape
De uitvoer is:
(2500, 13)
Het vormresultaat vertelt ons dat er 2500 items (of rijen) in de dataset en 13 kolommen zijn. Omdat we weten dat er รฉรฉn doelkolom is, betekent dit dat we 12 functiekolommen hebben.
We kunnen nu de doelvariabele, het pompoenzaad, onderzoeken Class
. Omdat we die variabele zullen voorspellen, is het interessant om te zien hoeveel monsters van elk pompoenzaad we hebben. Gewoonlijk geldt: hoe kleiner het verschil tussen het aantal instanties in onze klassen, hoe evenwichtiger onze steekproef is en hoe beter onze voorspellingen.
Deze inspectie kan worden gedaan door elk zaadmonster te tellen met de value_counts()
methode:
df['Class'].value_counts()
De bovenstaande code wordt weergegeven:
รerรงevelik 1300
รrgรผp Sivrisi 1200
Name: Class, dtype: int64
We kunnen zien dat er 1300 monsters zijn van de erรงevelik zaad en 1200 monsters van de rgรผp Sivrisi zaad. Merk op dat het verschil tussen hen 100 monsters is, een heel klein verschil, wat goed voor ons is en aangeeft dat het niet nodig is om het aantal monsters opnieuw in evenwicht te brengen.
Laten we ook eens kijken naar de beschrijvende statistieken van onze functies met de describe()
methode om te zien hoe goed de gegevens zijn verdeeld. We zullen de resulterende tabel ook transponeren met T
om het vergelijken van statistieken gemakkelijker te maken:
df.describe().T
De resulterende tabel is:
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
Door naar de tabel te kijken, bij het vergelijken van de gemiddelde en standaardafwijking (std
) kolommen, is te zien dat de meeste kenmerken een gemiddelde hebben dat ver van de standaarddeviatie ligt. Dat geeft aan dat de gegevenswaarden niet geconcentreerd zijn rond de gemiddelde waarde, maar meer verspreid eromheen - met andere woorden, ze hebben hoge variabiliteit.
Ook bij het kijken naar de minimum (min
) en maximaal (max
) kolommen, sommige functies, zoals Area
en Convex_Area
, hebben grote verschillen tussen minimum- en maximumwaarden. Dit betekent dat die kolommen zeer kleine gegevens hebben en ook zeer grote gegevenswaarden, of hogere amplitude tussen gegevenswaarden.
Met een hoge variabiliteit, hoge amplitude en kenmerken met verschillende meeteenheden, zouden de meeste van onze gegevens baat hebben bij dezelfde schaal voor alle kenmerken of als geschubd. Het schalen van gegevens centreert gegevens rond het gemiddelde en vermindert de variantie.
Dit scenario geeft waarschijnlijk ook aan dat er uitschieters en extreme waarden in data zitten. Dus het is het beste om wat te hebben uitbijter behandeling naast het schalen van de gegevens.
Er zijn enkele machine learning-algoritmen, bijvoorbeeld op bomen gebaseerde algoritmen zoals: Willekeurige bosclassificatie, die niet worden beรฏnvloed door hoge gegevensvariantie, uitbijters en extreme waarden. Logistische regressie is anders, het is gebaseerd op een functie die onze waarden categoriseert, en de parameters van die functie kunnen worden beรฏnvloed door waarden die buiten de algemene gegevenstrend vallen en een hoge variantie hebben.
We zullen meer over logistische regressie begrijpen wanneer we het kunnen implementeren. Voorlopig kunnen we onze gegevens blijven verkennen.
Opmerking: Er is een populair gezegde in de informatica: "Vuilnis erin, afval eruit" (GIGO), dat zeer geschikt is voor machine learning. Dit betekent dat wanneer we afvalgegevens hebben - metingen die de verschijnselen op zich niet beschrijven, gegevens die niet werden begrepen en goed voorbereid volgens het soort algoritme of model, waarschijnlijk een onjuiste uitvoer genereren die niet werkt op een dagelijkse basis.
Dit is een van de redenen waarom het verkennen, begrijpen van gegevens en hoe het gekozen model werkt zo belangrijk zijn. Door dat te doen, kunnen we voorkomen dat we afval in ons model stoppen - er waarde in plaatsen en er waarde uit halen.
De gegevens visualiseren
Tot nu toe hebben we met de beschrijvende statistieken een ietwat abstracte momentopname van enkele kwaliteiten van de gegevens. Een andere belangrijke stap is om het te visualiseren en onze hypothese van hoge variantie, amplitude en uitbijters te bevestigen. Om te zien of wat we tot nu toe hebben waargenomen in de gegevens wordt weergegeven, kunnen we enkele grafieken plotten.
Het is ook interessant om te zien hoe de kenmerken zich verhouden tot de twee klassen die zullen worden voorspeld. Laten we hiervoor de . importeren seaborn
pakket en gebruik de pairplot
grafiek om naar elke functieverdeling en elke klassenscheiding per functie te kijken:
import seaborn as sns
sns.pairplot(data=df, hue='Class')
Opmerking: Het kan even duren voordat de bovenstaande code is uitgevoerd, aangezien de pairplot scatterplots van alle features combineert (dat kan) en ook de feature-distributies weergeeft.
Als we naar de pairplot kijken, kunnen we zien dat in de meeste gevallen de punten van de รerรงevelik
klasse zijn duidelijk gescheiden van de punten van de รrgรผp Sivrisi
klas. Ofwel de punten van de ene klasse zijn naar rechts wanneer de anderen naar links zijn, of sommige zijn omhoog terwijl de anderen naar beneden zijn. Als we een soort curve of lijn zouden gebruiken om klassen te scheiden, laat dit zien dat het gemakkelijker is om ze te scheiden, als ze gemengd waren, zou classificatie een moeilijkere taak zijn.
In het Eccentricity
, Compactness
en Aspect_Ration
kolommen, kunnen sommige punten die "geรฏsoleerd" zijn of afwijken van de algemene gegevenstrend - uitbijters - ook gemakkelijk worden opgemerkt.
Als u naar de diagonaal van linksboven naar rechtsonder in de grafiek kijkt, ziet u dat de gegevensverdelingen ook een kleurcode hebben volgens onze klassen. De verdelingsvormen en de afstand tussen beide curven zijn andere indicatoren van hoe scheidbaar ze zijn - hoe verder van elkaar, hoe beter. In de meeste gevallen zijn ze niet over elkaar heen gelegd, wat inhoudt dat ze gemakkelijker te scheiden zijn, wat ook bijdraagt โโaan onze taak.
In volgorde kunnen we ook de boxplots van alle variabelen plotten met de sns.boxplot()
methode. Meestal is het handig om de boxplots horizontaal te oriรซnteren, zodat de vormen van de boxplots hetzelfde zijn als de distributievormen, dat kunnen we doen met de orient
argument:
sns.boxplot(data=df, orient='h')
Merk in de bovenstaande plot op dat: Area
en Convex_Area
hebben zo'n hoge magnitude in vergelijking met de magnitudes van de andere kolommen, dat ze de andere boxplots verpletteren. Om alle boxplots te kunnen bekijken, kunnen we de features schalen en opnieuw plotten.
Laten we, voordat we dat doen, begrijpen dat als er waarden zijn van functies die nauw verwant zijn aan andere waarden, bijvoorbeeld โ als er waarden zijn die ook groter worden wanneer andere functiewaarden groter worden, met een positieve correlatie; of als er waarden zijn die het tegenovergestelde doen, worden kleiner terwijl andere waarden kleiner worden, met a negatieve correlatie.
Dit is belangrijk om naar te kijken, omdat het hebben van sterke relaties in gegevens kan betekenen dat sommige kolommen zijn afgeleid van andere kolommen of een vergelijkbare betekenis hebben als ons model. Als dat gebeurt, kunnen de modelresultaten worden overschat en willen we resultaten die dichter bij de werkelijkheid liggen. Als er sterke correlaties zijn, betekent dit ook dat we het aantal kenmerken kunnen verminderen en minder kolommen kunnen gebruiken, waardoor het model meer wordt karig.
Opmerking: De standaardcorrelatie berekend met de corr()
methode is de Pearson-correlatiecoรซfficiรซnt. Deze coรซfficiรซnt wordt aangegeven wanneer gegevens kwantitatief zijn, normaal verdeeld, geen uitbijters hebben en een lineair verband hebben.
Een andere keuze zou zijn om te berekenen Spearmans correlatiecoรซfficiรซnt. De Spearman-coรซfficiรซnt wordt gebruikt wanneer gegevens ordinaal en niet-lineair zijn, een willekeurige verdeling hebben en uitbijters hebben. Merk op dat onze gegevens niet helemaal passen in de aannames van Pearson of Spearman (er zijn ook meer correlatiemethoden, zoals die van Kendall). Aangezien onze gegevens kwantitatief zijn en het voor ons belangrijk is om de lineaire relatie te meten, zullen we de coรซfficiรซnt van Pearson gebruiken.
Laten we eens kijken naar de correlaties tussen variabelen en dan kunnen we de gegevens voorbewerken. We berekenen de correlaties met de corr()
methode en visualiseer ze met Seaborn's heatmap()
. Het standaardformaat van de heatmap is meestal klein, dus we zullen importeren matplotlib
(algemene visualisatie-engine/bibliotheek waarop Seaborn is gebouwd) en verander de grootte met figsize
:
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 10))
correlations = df.corr()
sns.heatmap(correlations, annot=True)
In deze heatmap zijn de waarden die dichter bij 1 of -1 liggen de waarden waar we op moeten letten. Het eerste geval duidt op een hoge positieve correlatie en het tweede geval duidt op een hoge negatieve correlatie. Beide waarden, indien niet hoger dan 0.8 of -0.8 zullen gunstig zijn voor ons logistische regressiemodel.
Wanneer er hoge correlaties zijn, zoals die van 0.99
tussen Aspec_Ration
en Compactness
, dit betekent dat we ervoor kunnen kiezen om alleen te gebruiken Aspec_Ration
of alleen Compactness
, in plaats van beide (aangezien ze bijna gelijk zouden zijn) voorspellers van elkaar). Hetzelfde geldt voor Eccentricity
en Compactness
met een -0.98
correlatie, voor Area
en Perimeter
met een 0.94
correlatie en enkele andere kolommen.
Voorbewerking van de gegevens
Omdat we de gegevens al een tijdje hebben verkend, kunnen we beginnen met de voorbewerking ervan. Laten we nu alle functies voor de klassevoorspelling gebruiken. Na het verkrijgen van een eerste model, een baseline, kunnen we enkele van de sterk gecorreleerde kolommen verwijderen en vergelijken met de baseline.
De functiekolommen zijn onze X
data en de klassenkolom, onze y
doel gegevens:
y = df['Class']
X = df.drop(columns=['Class'], axis=1)
Categorische functies omzetten in numerieke functies
met betrekking tot onze Class
kolom - de waarden zijn geen getallen, dit betekent dat we ze ook moeten transformeren. Er zijn veel manieren om deze transformatie te doen; hier zullen we de . gebruiken replace()
methode en vervang รerรงevelik
naar 0
en รrgรผp Sivrisi
naar 1
.
y = y.replace('รerรงevelik', 0).replace('รrgรผp Sivrisi', 1)
Houd de kaart in gedachten! Wanneer u resultaten van uw model leest, wilt u deze in ieder geval in uw gedachten terugzetten, of terug in de klassenaam voor andere gebruikers.
Gegevens opdelen in trein- en testsets
In onze verkenning hebben we opgemerkt dat de functies moesten worden geschaald. Als we de schaal nu zouden doen, of op een automatische manier, zouden we waarden schalen met het geheel van X
en y
. In dat geval zouden we introduceren data lekkage, omdat de waarden van de binnenkort te testen set de schaal zouden hebben beรฏnvloed. Gegevenslekkage is een veelvoorkomende oorzaak van niet-reproduceerbare resultaten en illusoire hoge prestaties van ML-modellen.
Nadenken over de schaal laat zien dat we eerst moeten splitsen X
en y
gegevens verder in trein- en testsets en vervolgens naar geschikt een scaler op de trainingsset, en om transformeren zowel de trein als de testsets (zonder dat de testset ooit invloed heeft op de scaler die dit doet). Hiervoor gebruiken we Scikit-Learn's train_test_split()
methode:
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)
omgeving test_size=.25
zorgt ervoor dat we 25% van de gegevens gebruiken voor testen en 75% voor training. Dit kan worden weggelaten, zodra het de standaardsplitsing is, maar de Pythonisch manier om code te schrijven adviseert dat "expliciet beter is dan impliciet".
Opmerking: De zin "expliciet is beter dan impliciet" is een verwijzing naar: De zen van Python, of PEP20. Het bevat enkele suggesties voor het schrijven van Python-code. Als die suggesties worden opgevolgd, wordt de code in overweging genomen Pythonisch. U kunt er meer over weten hier.
Na het splitsen van de gegevens in trein- en testsets, is het een goede gewoonte om te kijken hoeveel records er in elke set zitten. Dat kan met de shape
attribuut:
X_train.shape, X_test.shape, y_train.shape, y_test.shape
Dit toont:
((1875, 12), (625, 12), (1875,), (625,))
We kunnen zien dat we na de splitsing 1875 records hebben voor training en 625 voor testen.
Gegevens schalen
Zodra we onze trein- en testsets klaar hebben, kunnen we doorgaan met het schalen van de gegevens met Scikit-Learn StandardScaler
object (of andere scalers geleverd door de bibliotheek). Om lekkage te voorkomen, wordt de scaler op de X_train
gegevens en de treinwaarden worden vervolgens gebruikt om zowel de trein- als testgegevens te schalen โ of te transformeren:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
Aangezien je meestal belt:
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
De eerste twee regels kunnen worden samengevouwen met een enkelvoud fit_transform()
call, die de scaler op de set past, en in รฉรฉn keer transformeert. We kunnen nu de boxplot-grafieken reproduceren om het verschil te zien na het schalen van gegevens.
Aangezien de schaling kolomnamen verwijdert, kunnen we voorafgaand aan het plotten treingegevens in een dataframe met kolomnamen organiseren om de visualisatie te vergemakkelijken:
column_names = df.columns[:12]
X_train = pd.DataFrame(X_train, columns=column_names)
sns.boxplot(data=X_train, orient='h')
We kunnen eindelijk al onze boxplots zien! Merk op dat ze allemaal uitbijters hebben, en de kenmerken die een verdeling presenteren die verder van normaal is (die krommen hebben die ofwel scheef naar links of naar rechts zijn), zoals Solidity
, Extent
, Aspect_Ration
en Compactedness
, zijn dezelfde die hogere correlaties hadden.
Uitschieters verwijderen met IQR-methode
We weten al dat logistische regressie kan worden beรฏnvloed door uitbijters. Een van de manieren om ze te behandelen is het gebruik van een methode genaamd Interkwartielbereik or I.Q.R.. De eerste stap van de IQR-methode is om onze treingegevens in vier delen te verdelen, kwartielen genaamd. Het eerste kwartiel, Q1, bedraagt โโ25% van de gegevens, de tweede, Q2, tot 50%, de derde, Q3, tot 75%, en de laatste, Q4, tot 100%. De boxen in de boxplot worden gedefinieerd door de IQR-methode en zijn daar een visuele weergave van.
Bij een horizontale boxplot markeert de verticale lijn aan de linkerkant 25% van de gegevens, de verticale lijn in het midden, 50% van de gegevens (of de mediaan) en de laatste verticale lijn aan de rechterkant, 75% van de gegevens . Hoe gelijker in grootte beide vierkanten gedefinieerd door de verticale lijnen โ of hoe meer de verticale mediaanlijn in het midden is โ betekent dat onze gegevens dichter bij de normale verdeling liggen of minder scheef zijn, wat handig is voor onze analyse.
Naast de IQR-box zijn er aan weerszijden ook horizontale lijnen. Die lijnen markeren de minimum- en maximumverdelingswaarden gedefinieerd door
$$
Minimum = Q1 โ 1.5*IQR
$$
en
$$
Maximaal = Q3 + 1.5*IQR
$$
IQR is precies het verschil tussen Q3 en Q1 (of Q3 โ Q1) en het is het meest centrale datapunt. Dat is de reden waarom we bij het vinden van de IQR uiteindelijk de uitbijters filteren in de data-uiteinden, of in de minimum- en maximumpunten. Boxplots geven ons een voorproefje van wat het resultaat van de IQR-methode zal zijn.
We kunnen panda's gebruiken quantile()
methode om onze kwantielen te vinden, en iqr
van het scipy.stats
pakket om het interkwartielgegevensbereik voor elke kolom te verkrijgen:
from scipy.stats import iqr
Q1 = X_train.quantile(q=.25)
Q3 = X_train.quantile(q=.75)
IQR = X_train.apply(iqr)
Nu we Q1, Q3 en IQR hebben, kunnen we de waarden uitfilteren die dichter bij de mediaan liggen:
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]
Nadat we onze trainingsrijen hebben gefilterd, kunnen we zien hoeveel ervan nog in de gegevens staan โโmet shape
:
X_train.shape
Dit resulteert in:
(1714, 12)
We kunnen zien dat het aantal rijen na filtering van 1875 naar 1714 ging. Dit betekent dat 161 rijen uitschieters bevatten of 8.5% van de gegevens.
Opmerking: Het wordt aanbevolen dat het filteren van uitbijters, het verwijderen van NaN-waarden en andere acties waarbij gegevens worden gefilterd en opgeschoond, onder of tot 10% van de gegevens blijft. Probeer andere oplossingen te bedenken als uw filtering of verwijdering meer dan 10% van uw gegevens bedraagt.
Na het verwijderen van uitbijters zijn we bijna klaar om data in het model op te nemen. Voor de modelaanpassing zullen we treingegevens gebruiken. X_train
wordt gefilterd, maar hoe zit het met? y_train
?
y_train.shape
Dit levert op:
(1875,)
Let erop dat y_train
heeft nog steeds 1875 rijen. We moeten overeenkomen met het aantal y_train
rijen naar het aantal X_train
rijen en niet zomaar willekeurig. We moeten de y-waarden verwijderen van de exemplaren van pompoenpitten die we hebben verwijderd, die waarschijnlijk verspreid zijn door de y_train
set. de gefilterde X_train
stil heeft zijn originele indices en de index heeft hiaten waar we uitbijters hebben verwijderd! We kunnen dan de index van de X_train
DataFrame om te zoeken naar de corresponderende waarden in y_train
:
y_train = y_train.iloc[X_train.index]
Nadat we dat hebben gedaan, kunnen we kijken naar de y_train
vorm opnieuw:
y_train.shape
Welke outputs:
(1714,)
Bekijk onze praktische, praktische gids voor het leren van Git, met best-practices, door de industrie geaccepteerde normen en bijgevoegd spiekbriefje. Stop met Googlen op Git-commando's en eigenlijk leren het!
Nu, y_train
heeft ook 1714 rijen en ze zijn hetzelfde als de X_train
rijen. We zijn eindelijk klaar om ons logistische regressiemodel te maken!
Het logistieke regressiemodel implementeren
Het moeilijke deel is gedaan! Voorbewerking is meestal moeilijker dan modelontwikkeling, als het gaat om het gebruik van bibliotheken zoals Scikit-Learn, die de toepassing van ML-modellen hebben gestroomlijnd tot slechts een paar regels.
Eerst importeren we de LogisticRegression
class en instantiรซren, het creรซren van een LogisticRegression
voorwerp:
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression(random_state=SEED)
Ten tweede passen we onze treingegevens aan de logreg
model met de fit()
methode, en voorspellen onze testgegevens met de predict()
methode, waarbij de resultaten worden opgeslagen als y_pred
:
logreg.fit(X_train.values, y_train)
y_pred = logreg.predict(X_test)
We hebben al voorspellingen gedaan met ons model! Laten we eens kijken naar de eerste 3 rijen in X_train
om te zien welke gegevens we hebben gebruikt:
X_train[:3]
De bovenstaande code geeft het volgende weer:
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
En bij de eerste 3 voorspellingen in y_pred
om de resultaten te zien:
y_pred[:3]
Dit resulteert in:
array([0, 0, 0])
Voor die drie rijen waren onze voorspellingen dat het zaden van de eerste klasse waren, รerรงevelik
.
met logistische regressie, in plaats van de laatste klasse te voorspellen, zoals 0
, kunnen we ook de kans voorspellen dat de rij betrekking heeft op de 0
klas. Dit is wat er feitelijk gebeurt wanneer logistische regressie gegevens classificeert, en de predict()
methode passeert deze voorspelling vervolgens door een drempelwaarde om een โโ"harde" klasse te retourneren. Om de kans te voorspellen om tot een klasse te behoren, predict_proba()
is gebruikt:
y_pred_proba = logreg.predict_proba(X_test)
Laten we ook eens kijken naar de eerste 3 waarden van de y-kansenvoorspellingen:
y_pred_proba[:3]
Welke outputs:
# class 0 class 1
array([[0.54726628, 0.45273372],
[0.56324527, 0.43675473],
[0.86233349, 0.13766651]])
In plaats van drie nullen hebben we nu รฉรฉn kolom voor elke klasse. In de kolom links, beginnend met 0.54726628
, zijn de waarschijnlijkheden van de gegevens die betrekking hebben op de klasse 0
; en in de rechterkolom, beginnend met 0.45273372
, is de kans dat het betrekking heeft op de klasse 1
.
Opmerking: Dit verschil in classificatie staat ook bekend als: hard en zacht voorspelling. Harde voorspelling zet de voorspelling in een klasse, terwijl zachte voorspellingen de waarschijnlijkheid van de instantie die tot een klasse behoort.
Er is meer informatie over hoe de voorspelde output is gemaakt. Het was eigenlijk niet 0
, maar 55% kans op klasse 0
, en 45% kans op klasse 1
. Dit laat zien hoe de eerste drie X_test
datapunten, met betrekking tot klasse 0
, zijn alleen echt duidelijk met betrekking tot het derde gegevenspunt, met een waarschijnlijkheid van 86% - en niet zozeer voor de eerste twee gegevenspunten.
Bij het communiceren van bevindingen met behulp van ML-methoden is het meestal het beste om een โโzachte klasse te retourneren, en de bijbehorende waarschijnlijkheid als de "vertrouwen" van die classificatie.
We zullen meer praten over hoe dat wordt berekend als we dieper op het model ingaan. Op dit moment kunnen we doorgaan naar de volgende stap.
Het model evalueren met classificatierapporten
De derde stap is om te zien hoe het model presteert op testgegevens. We kunnen Scikit-Learn . importeren classification_report()
en passeer onze y_test
en y_pred
als argumenten. Daarna kunnen we de reactie afdrukken.
Het classificatierapport bevat de meest gebruikte classificatiestatistieken, zoals: nauwkeurigheid, herinneren, f1-score en nauwkeurigheid.
- precisie: om te begrijpen welke correcte voorspellingswaarden door onze classifier als correct werden beschouwd. Precisie zal die echte positieve waarden delen door alles dat als positief werd voorspeld:
$$
precisie = frac{text{true positive}}{text{true positive} + tekst{false positive}}
$$
- Terugroepen: om te begrijpen hoeveel van de echte positieven werden geรฏdentificeerd door onze classifier. De terugroepactie wordt berekend door de echte positieven te delen door alles wat als positief had moeten worden voorspeld:
$$
recall = frac{tekst{true positive}}{text{true positive} + text{false negative}}
$$
- F1-score: is de gebalanceerde of harmonisch gemiddelde van precisie en herinnering. De laagste waarde is 0 en de hoogste is 1. Wanneer
f1-score
is gelijk aan 1, dit betekent dat alle klassen correct zijn voorspeld - dit is een zeer moeilijke score om te verkrijgen met echte gegevens:
$$
tekst{f1-score} = 2* frac{tekst{precisie} * tekst{herinnering}}{tekst{precisie} + tekst{herinnering}}
$$
- Nauwkeurigheid: beschrijft hoeveel voorspellingen onze classifier juist had. De laagste nauwkeurigheidswaarde is 0 en de hoogste is 1. Die waarde wordt meestal vermenigvuldigd met 100 om een โโpercentage te verkrijgen:
$$
nauwkeurigheid = frac{text{aantal correcte voorspellingen}}{text{totaal aantal voorspellingen}}
$$
Opmerking: Het is buitengewoon moeilijk om 100% nauwkeurigheid te verkrijgen op echte gegevens, als dat gebeurt, houd er dan rekening mee dat er een lekkage of iets verkeerds kan gebeuren - er is geen consensus over een ideale nauwkeurigheidswaarde en het is ook contextafhankelijk. Een waarde van 70%, wat betekent dat de classifier fouten maakt op 30% van de gegevens, of meer dan 70% is meestal voldoende voor de meeste modellen.
from sklearn.metrics import classification_report
cr = classification_report(y_test, y_pred)
print(cr)
We kunnen dan kijken naar de output van het classificatierapport:
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
Dit is ons resultaat. Let erop dat precision
, recall
, f1-score
en accuracy
statistieken zijn allemaal erg hoog, boven de 80%, wat ideaal is - maar die resultaten werden waarschijnlijk beรฏnvloed door hoge correlaties en zullen op de lange termijn niet standhouden.
De nauwkeurigheid van het model is 86%, wat betekent dat de classificatie 14% van de tijd verkeerd is. We hebben die algemene informatie, maar het zou interessant zijn om te weten of de 14% fouten gebeuren met betrekking tot de classificatie van klasse 0
of klasse 1
. Om te identificeren welke klassen ten onrechte als welke zijn geรฏdentificeerd, en in welke frequentie, kunnen we een berekenen en plotten verwarring matrix van de voorspellingen van ons model.
Het model evalueren met een verwarringsmatrix
Laten we de verwarringsmatrix berekenen en plotten. Nadat we dat hebben gedaan, kunnen we elk deel ervan begrijpen. Om de verwarringsmatrix te plotten, gebruiken we Scikit-Learn confusion_matrix()
, die we importeren uit de metrics
module.
De verwarringsmatrix is โโgemakkelijker te visualiseren met een Seaborn heatmap()
. Dus, na het genereren ervan, zullen we onze verwarringsmatrix doorgeven als argument voor de heatmap:
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d')
- Verwarring Matrix: de matrix laat zien hoeveel steekproeven het model goed of fout had voor elke klasse. De waarden die correct en correct voorspeld waren, worden genoemd echte positieven, en degenen die werden voorspeld als positief maar niet positief waren, worden genoemd valse positieven. Dezelfde nomenclatuur van echte negatieven en valse negatieven wordt gebruikt voor negatieve waarden;
Door naar de verwarringsmatrixplot te kijken, kunnen we zien dat we 287
waarden die waren 0
en voorspeld als 0
- of echte positieven voor de les 0
(de รerรงevelik-zaden). We hebben ook 250
echte positieven voor de klas 1
(รrgรผp Sivrisi-zaden). De echte positieven bevinden zich altijd in de matrixdiagonaal die van linksboven naar rechtsonder loopt.
We hebben ook 29
waarden die zouden moeten zijn 0
, maar voorspeld als 1
(valse positieven) en 59
waarden die waren 1
en voorspeld als 0
(valse negatieven). Met die cijfers kunnen we begrijpen dat de fout die het model het meest maakt, is dat het valse negatieven voorspelt. Dus het kan er uiteindelijk toe leiden dat een รrgรผp Sivrisi-zaad als een รerรงevelik-zaad wordt geclassificeerd.
Dit soort fouten wordt ook verklaard door de 81% terugroepactie van de klas 1
. Merk op dat de metrieken met elkaar verbonden zijn. En het verschil in de terugroepactie komt van het hebben van 100 minder monsters van de รrgรผp Sivrisi-klasse. Dit is een van de implicaties van het hebben van slechts een paar monsters minder dan de andere klasse. Om het terugroepen verder te verbeteren, kunt u experimenteren met klassegewichten of meer รrgรผp Sivrisi-monsters gebruiken.
Tot nu toe hebben we de meeste traditionele stappen in de datawetenschap uitgevoerd en het logistische regressiemodel als een zwarte doos gebruikt.
Opmerking: Als je verder wilt gaan, gebruik dan Kruisvalidatie (CV) en raster zoeken om respectievelijk te zoeken naar het model dat het meest generaliseert met betrekking tot gegevens, en de beste modelparameters die vรณรณr de training worden gekozen, of hyperparameters.
Idealiter zou u met CV en Grid Search ook een aaneengeschakelde manier kunnen implementeren om gegevensvoorbewerkingsstappen, gegevenssplitsing, modellering en evaluatie uit te voeren - wat gemakkelijk wordt gemaakt met Scikit-Learn pijpleidingen.
Nu is het tijd om de zwarte doos te openen en erin te kijken, om dieper te begrijpen hoe logistische regressie werkt.
Dieper ingaan op hoe logistieke regressie echt werkt
De regressie woord is er niet per ongeluk, om te begrijpen wat logistische regressie doet, kunnen we ons herinneren wat zijn broer of zus, lineaire regressie, doet met de gegevens. De lineaire regressieformule was de volgende:
$$
y = b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n
$$
waarin b0 was de regressie onderscheppen, b1 de coรซfficiรซnt en x1 de data.
Die vergelijking resulteerde in een rechte lijn die werd gebruikt om nieuwe waarden te voorspellen. De introductie herinnerend, is het verschil nu dat we geen nieuwe waarden voorspellen, maar een klasse. Dus die rechte lijn moet veranderen. Met logistische regressie introduceren we een niet-lineariteit en de voorspelling wordt nu gedaan met behulp van een curve in plaats van een lijn:
Merk op dat terwijl de lineaire regressielijn doorgaat en is gemaakt van continue oneindige waarden, de logistische regressiecurve in het midden kan worden verdeeld en extremen heeft in 0 en 1 waarden. Die "S"-vorm is de reden waarom het gegevens classificeert - de punten die dichterbij zijn of op het hoogste uiteinde vallen, behoren tot klasse 1, terwijl de punten die in het onderste kwadrant of dichter bij 0 liggen, tot klasse 0 behoren. de "S" is het midden tussen 0 en 1, 0.5 - het is de drempel voor de logistische regressiepunten.
We begrijpen het visuele verschil tussen logistische en lineaire regressie al, maar hoe zit het met de formule? De formule voor logistische regressie is de volgende:
$$
y = b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n
$$
Het kan ook worden geschreven als:
$$
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)}}
$$
Of zelfs worden geschreven als:
$$
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 + ldots + b_n * x_n)}}
$$
In de bovenstaande vergelijking hebben we de kans op invoer, in plaats van de waarde. Het heeft 1 als teller, dus het kan resulteren in een waarde tussen 0 en 1, en 1 plus een waarde in de noemer, zodat de waarde 1 is en zoiets - dit betekent dat het resultaat van de hele breuk niet groter kan zijn dan 1 .
En wat is de waarde die in de noemer staat? Het is e, de basis van de natuurlijke logaritme (ongeveer 2.718282), verheven tot de macht van lineaire regressie:
$$
e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}
$$
Een andere manier om het te schrijven zou zijn:
$$
ln links( frac{p}{1-p} rechts) = {(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}
$$
In die laatste vergelijking, ln is de natuurlijke logaritme (grondtal e) en p is de kans, dus de logaritme van de kans op het resultaat is hetzelfde als het lineaire regressieresultaat.
Met andere woorden, met het lineaire regressieresultaat en de natuurlijke logaritme kunnen we komen tot de waarschijnlijkheid dat een invoer al dan niet behoort tot een ontworpen klasse.
Het hele proces voor het afleiden van logistische regressie is als volgt:
$$
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_n * x_n)}}
$$
$$
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_n)}
$$
$$
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 links( frac{p}{1-p} rechts) = (b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldots + b_n * x_n)
$$
Dit betekent dat het logistische regressiemodel ook coรซfficiรซnten en een interceptwaarde heeft. Omdat het een lineaire regressie gebruikt en er een niet-lineaire component aan toevoegt met de natuurlijke logaritme (e
).
We kunnen de waarden van de coรซfficiรซnten en het snijpunt van ons model zien, op dezelfde manier als bij lineaire regressie, met behulp van coef_
en intercept_
eigendommen:
logreg.coef_
Die de coรซfficiรซnten van elk van de 12 kenmerken weergeeft:
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_
Dat resulteert in:
array([0.08735782])
Met de coรซfficiรซnten en intercept-waarden kunnen we de voorspelde kansen van onze gegevens berekenen. Laten we de eerste pakken X_test
waarden nogmaals, als voorbeeld:
X_test[:1]
Dit geeft de eerste rij van X_test
als een 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]])
Na de initiรซle vergelijking:
$$
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_n * x_n)}}
$$
In Python hebben we:
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
Dit resulteert in:
0.45273372469369133
Als we nog eens kijken naar de predict_proba
resultaat van de eerste X_test
lijn hebben we:
logreg.predict_proba(X_test[:1])
Dit betekent dat de oorspronkelijke logistische regressievergelijking ons de waarschijnlijkheid geeft van de invoer met betrekking tot klasse 1
, om erachter te komen welke kans voor klasse is 0
, kunnen we eenvoudig:
1 - px
Merk op dat beide px
en 1-px
zijn identiek aan predict_proba
resultaten. Dit is hoe logistische regressie wordt berekend en waarom regressie maakt deel uit van zijn naam. Maar hoe zit het met de term? logistieke?
De term logistieke komt van logit, wat een functie is die we al hebben gezien:
$$
ln links( frac{p}{1-p} rechts)
$$
We hebben het zojuist berekend met px
en 1-px
. Dit is de logit, ook wel log-kans omdat het gelijk is aan de logaritme van de kansen waarbij p
is een waarschijnlijkheid.
Conclusie
In deze gids hebben we een van de meest fundamentele classificatiealgoritmen voor machine learning bestudeerd, namelijk: logistische regressie.
Aanvankelijk implementeerden we logistische regressie als een zwarte doos met Scikit-Learn's machine learning-bibliotheek, en later begrepen we het stap voor stap om duidelijk te hebben waarom en waar de termen regressie en logistiek vandaan komen.
We hebben de gegevens ook verkend en bestudeerd, in het besef dat dit een van de meest cruciale onderdelen van een datawetenschapsanalyse is.
Vanaf hier zou ik je aanraden om te spelen met multiclass logistische regressie, logistische regressie voor meer dan twee klassen โ u kunt hetzelfde logistische regressie-algoritme toepassen op andere datasets met meerdere klassen, en de resultaten interpreteren.
Opmerking: Er is een goede verzameling datasets beschikbaar hier voor jou om mee te spelen.
Ik zou je ook aanraden om de L1 en L2 . te bestuderen regularisaties, ze zijn een manier om de hogere gegevens te 'straffen' zodat deze dichter bij normaal worden, waarbij de complexiteit van het model wordt tegengehouden, zodat het algoritme tot een beter resultaat kan komen. De Scikit-Learn-implementatie die we gebruikten, heeft standaard al L2-regularisatie. Een ander ding om naar te kijken is de verschillende oplossers, zoals lbgs
, die de prestaties van het logistische regressiealgoritme optimaliseren.
Het is ook belangrijk om te kijken naar de statistisch benadering van logistische regressie. Het heeft veronderstellingen over het gedrag van gegevens en over andere statistieken die moeten gelden om bevredigende resultaten te garanderen, zoals:
- de waarnemingen zijn onafhankelijk;
- er is geen multicollineariteit tussen verklarende variabelen;
- er zijn geen extreme uitschieters;
- er is een lineair verband tussen verklarende variabelen en de logit van de responsvariabele;
- de steekproefomvang voldoende groot is.
Merk op hoeveel van die veronderstellingen al zijn behandeld in onze analyse en behandeling van gegevens.
Ik hoop dat je blijft onderzoeken wat logistieke regressie te bieden heeft in al zijn verschillende benaderingen!