Ga naar hoofdinhoud

Preprocessing in Data Science (Deel 1): Centreren, schalen en KNN

Dit artikel legt uit waarom preprocessing in de machinelearning-pijplijn belangrijk is, door te laten zien hoe centreren en schalen de modelprestaties kunnen verbeteren.
· 11 min lezen

Verken met AI

Datapreprocessing is een parapluterm voor allerlei bewerkingen die data scientists gebruiken om hun data geschikt te maken voor wat ze ermee willen doen. Zo wil je, voordat je sentimentanalyse op Twitter-data uitvoert, misschien alle html-tags en witruimtes verwijderen, afkortingen uitschrijven en tweets opsplitsen in lijsten met de woorden die ze bevatten. Bij ruimtelijke data kun je schalen zodat het eenheidsonafhankelijk wordt, oftewel zodat je algoritme het niet uitmaakt of de oorspronkelijke metingen in miles of centimeters waren. Preprocessing vindt echter niet in een vacuüm plaats. Het is een middel en geen doel op zich, en er zijn geen keiharde regels: er zijn best practices, zoals we zullen zien, en je ontwikkelt gevoel voor wat werkt, maar uiteindelijk is preprocessing meestal onderdeel van een resultaatgerichte pijplijn en moet je de prestaties in context beoordelen.

In dit artikel gebruik ik het voorbeeld van het schalen van numerieke data (numerieke data: data die uit getallen bestaat, in tegenstelling tot categorieën/strings; schalen: de range van de data aanpassen met eenvoudige rekenkunde; meer hierover zo) om het belang te laten zien van preprocessing als onderdeel van een groter geheel: de machinelearning (ML)‑pijplijn. We bekijken hiervoor een praktijkvoorbeeld waarin schalen de modelprestaties kan verbeteren.

Ik introduceer eerst classificatieproblemen in ML en k-Nearest Neighbors, een van de simpelste algoritmen in zulke situaties. Om het belang van het schalen van numerieke data te waarderen, bespreek ik maatstaven voor modelprestaties en de concepten train- en testset. Je ziet al deze concepten en werkwijzen terug op een dataset waarin ik de kwaliteit van rode wijn probeer te classificeren. Ik plaats preprocessing ook op de meest zinvolle plek: aan het begin van een iteratieve data science‑pijplijn. Alle voorbeelden zijn in Python. Als je Python niet kent, bekijk dan onze DataCamp-cursussen hier. Ik gebruik de libraries pandas voor dataframes en scikit-learn voor machine learning.

Dit is deel één van een driedelige tutorial:

Deel 2: Centreren, schalen en logistieke regressie

Deel 3: Schalen van gesynthetiseerde data

Een korte introductie tot classificatieproblemen in machine learning

Dingen uit de waarneembare wereld classificeren en labelen is zo oud als de weg naar Rome. In de 4e eeuw v.Chr. ontwierp Aristoteles een classificatiesysteem voor levende wezens dat 2.000 jaar gebruikt werd. In de moderne tijd zien we classificatie vaak als een machinelearningtaak, en dan specifiek een supervised learning‑taak. Het basisprincipe van supervised learning is eenvoudig: we hebben een hoop data die bestaat uit predictorvariabelen en een doelvariabele. Het doel van supervised learning is een model bouwen dat ‘goed’ is in het voorspellen van de doelvariabele, gegeven de predictorvariabelen. Als de doelvariabele uit categorieën bestaat (bijv. ‘klik’ of ‘niet’, ‘maligne’ of ‘benigne’ tumor), noemen we de leertaak classificatie. Als de doelvariabele daarentegen continu varieert (bijv. huizenprijs), dan is het een regressietaak.

Een illustratief voorbeeld helpt: bekijk de hartziektedataset, met 75 predictorvariabelen, zoals ‘leeftijd’, ‘geslacht’ en ‘roker of niet’, en een doelvariabele die de aanwezigheid van hartziekte aangeeft en loopt van 0 (geen hartziekte) tot 4. Veel werk aan deze dataset richt zich op het onderscheiden van aanwezigheid versus afwezigheid van hartziekte. Dat is een classificatietaak. Als je zou proberen de daadwerkelijke waarde (0 t/m 4) van de doelvariabele te voorspellen, dan is dat een regressieprobleem (omdat de doelvariabele geordend is). Regressie bespreek ik in de volgende post. Hier kijk ik naar een van de eenvoudigste algoritmen voor classificatie: het algoritme van de k-Nearest Neighbors.

k-Nearest Neighbors voor classificatie in machine learning

Stel dat we gelabelde data hebben, bijvoorbeeld kenmerken van rode wijn (zoals alcoholgehalte, dichtheid, hoeveelheid citroenzuur, pH, enz.; dit zijn de predictorvariabelen) met als doelvariabele ‘Quality’ en labels ‘good’ & ‘bad’. Gegeven de kenmerken van een nieuwe, ongelabelde wijn is de classificatietaak dan om de ‘Quality’ te voorspellen. Als alle predictorvariabelen numeriek zijn (er zijn ook manieren voor categorische variabelen), kunnen we elke rij/wijn zien als een punt in een n‑dimensionale ruimte. In dat geval is k-Nearest Neighbors (k‑NN) een conceptueel en computationeel eenvoudig classificatiemethode: voor elke nieuwe, ongelabelde wijn berekenen we, voor een geheel getal k, de k dichtstbijzijnde buren in de n‑dimensionale ruimte van predictorvariabelen. Vervolgens kijken we naar de labels van deze k buren (dus ‘good’ of ‘bad’) en kennen we het label met de meeste stemmen toe aan de nieuwe wijn (bijv. als k = 5, 3 buren stemmen ‘good’ en 2 ‘bad’, dan labelt ons model de nieuwe wijn als ‘good’). Let op: het trainen van het model bestaat hier volledig uit het opslaan van de datapunten: er zijn geen parameters om te fitten!

Een visuele beschrijving van k-Nearest Neighbors

In de onderstaande afbeelding zie je k‑NN in 2D: hoe classificeer je het datapunt in het midden? Als k=3, classificeer je het als rood en als k=5, als groen.

 
k-nn in 2d example

k-NN-implementatie in Python (scikit-learn)

Laten we nu een voorbeeld van k‑NN in actie bekijken. We gebruiken de wine quality‑dataset: we importeren die in een pandas‑dataframe en plotten vervolgens histogrammen van de predictorvariabelen om gevoel voor de data te krijgen.
import pandas as pd%matplotlib inlineimport matplotlib.pyplot as pltplt.style.use('ggplot')df = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv ' , sep = ';')X = df.drop('quality' , 1).values # drop target variabley1 = df['quality'].valuespd.DataFrame.hist(df, figsize = [15,15]);
bar charts

Let eerst op de range van de predictorvariabelen: ‘free sulfur dioxide’ loopt van 0 tot ~70 en ‘volatile acidity’ van ~0 tot ~1,2. Meer specifiek: de eerste heeft een range die 2 ordes van grootte groter is dan die van de tweede. Elk algoritme, zoals k‑NN, dat gevoelig is voor afstanden tussen datapunten, kan daardoor onevenredig veel aandacht geven aan variabelen met een grotere range, zoals ‘free sulfur dioxide’—een variabele die, voor zover we weten, vooral ruis kan bevatten. Dit motiveert het schalen van onze data, waar we zo op terugkomen.

De doelvariabele is nu een ‘Quality’-rating van wijn en loopt van 3 tot 8. Voor de eenvoud van de uitleg maken we er een tweecategorievariabele van: ‘good’ (rating > 5) & ‘bad’ (rating <= 5). We plotten ook histogrammen van beide varianten van de doelvariabele om te zien wat er speelt.

y = y1 <= 5 # is the rating <= 5?# plot histograms of original target variable# and aggregated target variableplt.figure(figsize=(20,5));plt.subplot(1, 2, 1 );plt.hist(y1);plt.xlabel('original target value')plt.ylabel('count')plt.subplot(1, 2, 2);plt.hist(y)plt.xlabel('aggregated target value')plt.show()
bar charts
We zijn nu bijna klaar om k‑Nearest Neighbors uit te voeren. Eerst moeten we, als we willen vergelijken hoe ons model presteert met en zonder preprocessing, bepalen hoe we de ‘kwaliteit’ van een model meten:

k-Nearest Neighbors: hoe goed presteert het?

Er zijn meerdere prestatiemaatstaven voor classificatieproblemen. Het is cruciaal om te beseffen dat de keuze van maatstaf sterk afhangt van het domein en de vraag. In het geval van een dataset met gebalanceerde klassen (waar alle doelwaarden ongeveer even vaak voorkomen) kijken data scientists vaak naar accuracy als prestatiemaatstaf. Zoals we zullen zien, is accuracy zelfs de standaardscoremethode voor zowel k‑Nearest Neighbors als logistieke regressie in scikit‑learn. Wat is accuracy? Simpelweg het aantal correcte voorspellingen gedeeld door het totale aantal voorspellingen:
$$\text{Accuracy}=\frac{\text{Number of Correct Predictions}}{\text{Total Number of Predictions}}.$$

Opmerking: accuracy kan ook gedefinieerd worden aan de hand van een confusion matrix en wordt bij binaire classificatie vaak uitgedrukt in termen van true positives & false negatives; andere veelgebruikte maatstaven die uit de confusion matrix volgen zijn precision, het aantal true positives gedeeld door het aantal true & false positives, en recall, het aantal true positives gedeeld door het aantal true positives plus het aantal false negatives; een andere maatstaf, de F1‑score, is het harmonisch gemiddelde van precision en recall. Zie machine learning mastery voor een heldere uitleg; bekijk ook de Wikipedia‑pagina’s over de confusion matrix en de F1‑score.

k-Nearest Neighbors: prestaties in de praktijk en de train-test-split

Een maatstaf als accuracy is mooi, maar als we het model op alle data fitten, van welke dataset rapporteren we dan de accuracy? We willen immers een model dat goed generaliseert naar nieuwe data. Dus als we ons model trainen op een dataset D, dan kan het rapporteren van de accuracy op diezelfde data D het doen lijken alsof het model beter presteert dan het in werkelijkheid doet. Dit heet overfitting. Om dit te voorkomen trainen data scientists hun model vaak op een subset van de data, de trainingset, en evalueren ze de prestaties op de rest van de data, de testset. Precies dat gaan we hier doen! Als vuistregel gebruik je ongeveer 80% van de data voor training en 20% voor test. Laten we nu onze rodewijnkwaliteitsdata splitsen:
from sklearn.cross_validation import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)We now build the k-NN model, make predictions on the test set and compare these predictions to the ground truth in order to get a measure of model performance:from sklearn import neighbors, linear_modelknn = neighbors.KNeighborsClassifier(n_neighbors = 5)knn_model_1 = knn.fit(X_train, y_train)print('k-NN accuracy for test set: %f' % knn_model_1.score(X_test, y_test))k-NN accuracy for test set: 0.612500

It is worth reiterating that the default scoring method for k-NN in scikit-learn is
accuracy. An accuracy of 61% is not great but for an out of the box model without any preprocessing it's not horrible either. To check out a variety of other metrics, we can use scikit-learn's classification report also:

from sklearn.metrics import classification_reporty_true, y_pred = y_test, knn_model_1.predict(X_test)print(classification_report(y_true, y_pred))
 
             precision    recall  f1-score   support      False       0.66      0.64      0.65       179       True       0.56      0.57      0.57       141avg / total       0.61      0.61      0.61       320

We introduceren nu schalen en centreren, de meest basale methoden voor preprocessing van numerieke data, en kijken of en hoe ze onze modelprestaties beïnvloeden.

De mechanica van preprocessing: schalen en centreren

Voordat je een model zoals regressie (voorspellen van een continue variabele) of classificatie (voorspellen van een discrete variabele) op data draait, wil je bijna altijd enige preprocessing doen. Voor numerieke variabelen is het gebruikelijk om je data te normaliseren of te standaardiseren. Wat betekenen deze termen?

Normaliseren betekent simpelweg dat je een dataset schaalt zodat het minimum 0 is en het maximum 1. Dit bereiken we door elk datapunt $x$ te transformeren naar

$$x_{normalized} = \frac{x-x_{min}}{x_{max}-x_{min}}.$$

Standaardiseren is iets anders; het centreert de data rond 0 en schaalt met betrekking tot de standaardafwijking:

$$x_{standardized} = \frac{x-\mu}{\sigma},$$

waarbij $\mu$ en $\sigma$ respectievelijk het gemiddelde en de standaardafwijking van de dataset zijn. Let erop dat deze transformaties alleen de range van de data veranderen en niet de verdeling. Later wil je misschien andere transformaties gebruiken, zoals een logtransformatie of een Box‑Cox‑transformatie, om je data meer Gaussisch (klokvormig) te maken. Maar voordat we verder gaan, is het belangrijk te vragen: waarom schalen we onze data? Wanneer is het geschikter? Is het bijvoorbeeld belangrijker bij classificatie dan bij regressie?

Laten we eerst inzoomen op classificatie en zien hoe schalen de prestaties van k‑Nearest Neighbors beïnvloedt:

Preprocessing: schalen in de praktijk

Hieronder (i) schaal ik de data, (ii) pas ik k‑Nearest Neighbors toe en (iii) controleer ik de modelprestaties. Ik gebruik scikit‑learns scale‑functie, die alle features (kolommen) in de array die je doorgeeft standaardiseert.

from sklearn.preprocessing import scaleXs = scale(X)from sklearn.cross_validation import train_test_splitXs_train, Xs_test, y_train, y_test = train_test_split(Xs, y, test_size=0.2, random_state=42)knn_model_2 = knn.fit(Xs_train, y_train)print('k-NN score for test set: %f' % knn_model_2.score(Xs_test, y_test))print('k-NN score for training set: %f' % knn_model_2.score(Xs_train, y_train))y_true, y_pred = y_test, knn_model_2.predict(Xs_test)print(classification_report(y_true, y_pred))
 
 k-NN score for test set: 0.712500k-NN score for training set: 0.814699             precision    recall  f1-score   support      False       0.72      0.79      0.75       179       True       0.70      0.62      0.65       141avg / total       0.71      0.71      0.71       320

Al deze maatstaven verbeterden met 0,1, wat een verbetering van 16% is en significant! Zoals hierboven al gesuggereerd: vóór het schalen waren er meerdere predictorvariabelen met ranges die ordes van grootte verschilden, waardoor één of twee variabelen konden domineren in een algoritme als k‑NN. De twee belangrijkste redenen om je data te schalen zijn:

  1. Je predictorvariabelen kunnen sterk verschillende ranges hebben en in bepaalde situaties, zoals bij k‑NN, moet je dit mitigeren zodat bepaalde features het algoritme niet domineren;
  2. Je wilt dat je features eenheidsonafhankelijk zijn, dus niet afhankelijk van de meetschaal: jij kunt een feature in meters hebben en ik dezelfde feature in centimeters. Als we allebei onze data schalen, is deze feature voor ons beiden gelijk.

We hebben gezien welke essentiële plek preprocessing, in de vorm van schalen en centreren, inneemt in de data science‑pijplijn, en deden dat om een holistische benadering van machinelearninguitdagingen te bevorderen. In toekomstige artikelen wil ik dit uitbreiden naar andere soorten preprocessing, zoals transformaties van numerieke data en preprocessing van categorische data—beide onmisbare onderdelen van de gereedschapskist van elke data scientist. Voor het zover is, bekijk ik in het volgende artikel de rol van schalen bij regressiebenaderingen van classificatie. In het bijzonder kijk ik naar logistieke regressie, en je zult zien dat het resultaat heel anders is dan wat je zojuist zag bij k‑Nearest Neighbors.

In het interactieve venster hieronder kun je zelf met de data spelen. Begin met het aanpassen van de variabele n_neig, het aantal buren dat k‑NN gebruikt. Je kunt de data ook schalen door sc = True te zetten, als je dat wilt. Voer daarna het hele script uit om de accuracy van het model te rapporteren, samen met een classificatierapport.

import pandas as pdimport matplotlib.pyplot as pltfrom sklearn.cross_validation import train_test_splitfrom sklearn import neighborsfrom sklearn.preprocessing import scalefrom sklearn.metrics import classification_report # Set the the number of neighbors for k-NNn_neig = 5# Set sc = True if you want to scale your featuressc = False# Load datadf = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv ' , sep = ';')X = df.drop('quality' , 1).values# drop target variable# Here we scale, if desiredif sc == True: X = scale(X)# Target valuey1 = df['quality'].values# original target variabley = y1 <= 5# new target variable: is the rating <= 5?# Split the data into a test set and a training setX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# Train k-NN model and print performance on the test setknn = neighbors.KNeighborsClassifier(n_neighbors = n_neig)knn_model = knn.fit(X_train, y_train)y_true, y_pred = y_test, knn_model.predict(X_test)print('k-NN accuracy for test set: %f' % knn_model.score(X_test, y_test))print(classification_report(y_true, y_pred))

Woordenlijst

Supervised learning: De taak om een doelvariabele af te leiden uit predictorvariabelen. Bijvoorbeeld: de doelvariabele ‘aanwezigheid van hartziekte’ afleiden uit predictorvariabelen zoals ‘leeftijd’, ‘geslacht’ en ‘rookstatus’.

Classificatietaak: Een supervised learning‑taak is een classificatietaak als de doelvariabele uit categorieën bestaat (bijv. ‘klik’ of ‘niet’, ‘maligne’ of ‘benigne’ tumor).

Regressietaak: Een supervised learning‑taak is een regressietaak als de doelvariabele een continu variërende variabele is (bijv. prijs van een huis) of een geordende categorische variabele zoals ‘kwaliteitsrating van wijn’.

k-Nearest Neighbors: Een algoritme voor classificatietaken, waarbij een datapunt het label krijgt dat wordt bepaald door een meerderheid van stemmen van zijn k dichtstbijzijnde buren.

Preprocessing: Allerlei bewerkingen die data scientists gebruiken om hun data geschikt te maken voor wat ze ermee willen doen. Zo wil je, voordat je sentimentanalyse op Twitter‑data uitvoert, misschien alle html‑tags en witruimtes verwijderen, afkortingen uitschrijven en tweets opsplitsen in lijsten met de woorden die ze bevatten.

Centreren en schalen: Dit zijn vormen van preprocessing van numerieke data, dus data die uit getallen bestaat, in tegenstelling tot categorieën of strings, bijvoorbeeld; centreren van een variabele is het gemiddelde van de variabele van elk datapunt aftrekken zodat het nieuwe gemiddelde 0 is; schalen van een variabele is elk datapunt met een constante vermenigvuldigen om de range van de data te wijzigen. Zie de hoofdtekst voor het belang hiervan, met voorbeelden.

Dit artikel is gegenereerd vanuit een Jupyter‑notebook. Je kunt de notebook hier downloaden.

Onderwerpen

Gerelateerd