Eine große Her­aus­for­de­rung im Bereich Machine-Lear­ning stellt der Umgang mit unaus­ge­wo­ge­nen (engl. unba­lan­ced) Daten­sät­zen bei der Klas­si­fi­zie­rung dar. Dabei bezeich­net man einen Daten­satz als unaus­ge­wo­gen, wenn eine Klasse des Daten­sat­zes gegen­über den ande­ren deut­lich über-/un­ter­re­prä­sen­tiert ist.

In die­sem Zusam­men­hang stellt Betrugs­ent­de­ckung (engl. Fraud Detec­tion) ein beson­de­res Bei­spiel dar: Ein gro­ßer Teil der Daten des Daten­sat­zes, meist mehr als 90%, genannt Mehr­heits­klasse, reprä­sen­tiert ein „nor­ma­les“ Ver­hal­ten und ledig­lich ein sehr klei­ner Teil des Daten­sat­zes, genannt Min­der­heits­klasse, sollte als „Betrug“ klas­si­fi­ziert wer­den. In die­sem Sze­na­rio kann ein Model eine sehr gute Genau­ig­keit erzie­len, wenn schlicht jeder Daten­satz als „nor­mal“ klas­si­fi­ziert wird, die Genau­ig­keit ent­spricht dann gerade dem Anteil der tat­säch­lich „nor­ma­len“ Daten­sätze im Daten­satz. Auch wenn die­ses Modell auf den ers­ten Blick durch seine hohe Genau­ig­keit zu über­zeu­gen scheint, ist es doch völ­lig nutz­los, da es dem Anwen­der kei­ner­lei Zusatz­in­for­ma­tion lie­fert. Da die Genau­ig­keit in die­sem Fall also ein wenig hilf­rei­ches Maß für die Güte eines Klas­si­fi­zie­rers dar­stellt, wer­den andere Metri­ken zur Bewer­tung her­an­ge­zo­gen, wie auch im saracus-Blog zu lesen ist.

Die Ver­wen­dung einer ande­ren Metrik zur Bewer­tung der Güte eines Klas­si­fi­zie­rers ist aller­dings nur der erste Schritt, denn ist eine sol­che, den ent­spre­chen­den Anfor­de­run­gen genü­gende, Metrik bestimmt, muss dafür gesorgt wer­den, dass die Machine-Lear­ning Algo­rith­men auch bezüg­lich der Metrik gute Ergeb­nisse lie­fern. Viele der Machine-Lear­ning Algo­rith­men haben jedoch Schwie­rig­kei­ten mit der Ver­ar­bei­tung unaus­ge­wo­ge­ner Daten­sätze und erzie­len nur unge­nü­gende Resul­tate. Um die­sem Pro­blem zu begeg­nen, gibt es etwa­ige Vari­an­ten, um den Daten­satz zu balancieren.

Bei der Ver­wen­dung von Python bie­tet dabei das imba­lan­ced-learn Paket diverse Funk­tio­na­li­tä­ten, wel­che die Balan­cie­rung des Daten­sat­zes aus­füh­ren. In die­sem Blog­ein­trag soll daher ein kur­zer Über­blick über das Paket und seine Metho­den gege­ben werden.

Die Vor­aus­set­zun­gen für die Ver­wen­dung des Pake­tes sind gering. Es erfor­dert in der der­zei­ti­gen Ver­sion (0.4.3) eine Instal­la­tion der Pakete numpy (>= 1.8.2), scipy (>=0.13.3) und sci­kit-learn (>=0.20). Für die Instal­la­tion des imba­lan­ced-learn Pakets kann auf pip oder ana­conda zurück­ge­grif­fen werden.

Das Paket war­tet mit diver­sen Funk­tio­na­li­tä­ten auf, wel­che in vier Kate­go­rien unter­teilt wer­den können:

  • Over-Sam­pling der Minderheitsklasse
  • Under-Sam­pling der Mehrheitsklasse(n)
  • Kom­bi­na­tion von Under- und Over-Sampling
  • Ensem­ble-Klas­si­fi­zie­rung


Der Fokus die­ses Arti­kels soll auf den Funk­tio­na­li­tä­ten der ers­ten drei Kate­go­rien liegen.

Over-Sam­pling der Minderheitsklasse

Es ste­hen fünf Metho­den zur Auswahl:

  • Ran­dom mino­rity over-sam­pling with replacement
  • SMOTE – Syn­the­tic Mino­rity Over-sam­pling Technique
  • bSMOTE (1&2) – Bor­der­line SMOTE of types 1 and 2
  • SVM SMOTE – Sup­port Vec­tors SMOTE
  • ADASYN – Adap­tive Syn­the­tic Sam­pling approach for imba­lan­ced learning

Es wird ein unba­lan­cier­ter Test­da­ten­satz mit 2 Fea­tures gene­riert, um ver­schie­dene Over-Sam­pling Metho­den aus dem imba­lan­ced-learn Paket zu ver­an­schau­li­chen. Dabei kommt exem­pla­risch ein Linea­rer SVM Klas­si­fi­zie­rer zum Ein­satz, um die Unter­schiede der Over-Sam­pling Metho­den aufzuzeigen.

import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_classification

# erstelle unbalancierten Testdatensatz mit 2 Features
X, y = make_classification(n_samples=5000, n_features=2, n_informative=2,
                           n_redundant=0, n_repeated=0, n_classes=3,
                           n_clusters_per_class=1,
                           weights=[0.01, 0.05, 0.94],
                           class_sep=0.8, random_state=0)


# Hilfsfunktionen zur Darstellung der Ergebnisse als Plot
def make_meshgrid(x, y, h=.02):
    x_min, x_max = x.min() - 1, x.max() + 1
    y_min, y_max = y.min() - 1, y.max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    return xx, yy


def plot_contours(ax, clf, xx, yy, **params):
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    out = ax.contourf(xx, yy, Z, **params)
    return out


# wende lineare SVM auf den Datensatz an
model = svm.SVC(kernel='linear')
clf = model.fit(X, y)


# stelle Resultate grafisch dar
fig, ax = plt.subplots()
X0, X1 = X[:, 0], X[:, 1]
xx, yy = make_meshgrid(X0, X1)

plot_contours(ax, clf, xx, yy, cmap=plt.cm.coolwarm, alpha=0.8)
ax.scatter(X0, X1, c=y, cmap=plt.cm.coolwarm, s=20, edgecolors='k')
ax.set_ylabel('Feature 1')
ax.set_xlabel('Feature 2')
ax.set_xticks(())
ax.set_yticks(())
ax.set_title('Linear SVC with Raw Data')
ax.legend()
plt.show()

Um den Daten­satz zu balan­cie­ren ver­wen­den wir exem­pla­risch den Ran­dom Over Sam­pler, SMOTE und ADASYN.

import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_classification
from imblearn.over_sampling import RandomOverSampler

# erstelle unbalancierten Testdatensatz mit 2 Features
X, y = make_classification(n_samples=5000, n_features=2, n_informative=2,
                           n_redundant=0, n_repeated=0, n_classes=3,
                           n_clusters_per_class=1,
                           weights=[0.01, 0.05, 0.94],
                           class_sep=0.8, random_state=0)

# verwende den RandomOverSampler aus dem imblearn Paket, um den Datensatz zu balancieren
ros = RandomOverSampler(random_state=0)
X_resampled, y_resampled = ros.fit_resample(X, y)


# Hilfsfunktionen zur Darstellung der Ergebnisse als Plot
def make_meshgrid(x, y, h=.02):
    x_min, x_max = x.min() - 1, x.max() + 1
    y_min, y_max = y.min() - 1, y.max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    return xx, yy


def plot_contours(ax, clf, xx, yy, **params):
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    out = ax.contourf(xx, yy, Z, **params)
    return out

# wende lineare SVM auf den Datensatz an
model = svm.SVC(kernel='linear')
clf = model.fit(X_resampled, y_resampled)


# stelle Resultate grafisch dar
fig, ax = plt.subplots()
X0, X1 = X_resampled[:, 0], X_resampled[:, 1]
xx, yy = make_meshgrid(X0, X1)

plot_contours(ax, clf, xx, yy, cmap=plt.cm.coolwarm, alpha=0.8)
ax.scatter(X0, X1, c=y_resampled, cmap=plt.cm.coolwarm, s=20, edgecolors='k')
ax.set_ylabel('Feature 1')
ax.set_xlabel('Feature 2')
ax.set_xticks(())
ax.set_yticks(())
ax.set_title('Linear SVC with RandomOverSampler')
ax.legend()
plt.show()
Machine-Learning mit unausgewogenen Datensätzen imbalanced-learn (Python-Package) Bild3
Machine-Learning mit unausgewogenen Datensätzen imbalanced-learn (Python-Package) Bild2
Machine-Learning mit unausgewogenen Datensätzen imbalanced-learn (Python-Package) Bild4

Die Wahl der zu ver­wen­den­den Methode hat einen offen­sicht­li­chen Ein­fluss auf das Ergeb­nis der Klas­si­fi­zie­rung sei­tens der SVM. Man beachte zudem, dass die Ver­wen­dung der Over-Sam­pling Metho­den unkom­pli­ziert ist und sich zwi­schen den ein­zel­nen Metho­den nicht unter­schei­det, was die Anwend­bar­keit erhöht.

Under-Sam­pling der Mehrheitsklasse(n)

Es ste­hen elf Metho­den zur Auswahl:

  • Ran­dom majo­rity under-sam­pling with replacement
  • Extra­c­tion of majo­rity-mino­rity Tomek links
  • Under-sam­pling with Clus­ter Centroids
  • NearMiss-(1 & 2 & 3)
  • Con­den­sed Nea­rest Neighbour
  • One-Sided Sel­ec­tion
  • Neigh­bo­or­hood Clea­ning Rule
  • Edi­ted Nea­rest Neighbours
  • Ins­tance Hard­ness Threshold
  • Repea­ted Edi­ted Nea­rest Neighbours
  • All­KNN

Erneut wird ein unaus­ge­wo­ge­ner Test­da­ten­satz mit 2 Fea­tures betrach­tet, um eine Aus­wahl der Under-Sam­pling Metho­den zu prä­sen­tie­ren. Wie­der kommt ein Linea­rer SVM Klas­si­fi­zie­rer zum Ein­satz, um die Unter­schiede der jewei­li­gen Under-Sam­pling Metho­den darzustellen.

Zur Balan­cie­rung des Test­da­ten­sat­zes ver­wen­den wir exem­pla­risch Con­den­sed Nea­rest Neigh­bour, Ins­tance Hard­ness Thres­hold und Ran­dom Under Sampler.

import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_classification
from imblearn.under_sampling import InstanceHardnessThreshold
from sklearn.linear_model import LogisticRegression

# erstelle unbalancierten Testdatensatz mit 2 Features
X, y = make_classification(n_samples=5000, n_features=2, n_informative=2,
                           n_redundant=0, n_repeated=0, n_classes=3,
                           n_clusters_per_class=1,
                           weights=[0.01, 0.05, 0.94],
                           class_sep=0.8, random_state=0)

# verwende InstanceHardnessThreshold mit Logistischer Regression
# aus dem imblearn Paket, um den Datensatz zu balancieren
iht = InstanceHardnessThreshold(estimator=LogisticRegression(solver='lbfgs', multi_class='auto'))
X_resampled, y_resampled = iht.fit_resample(X, y)


# Hilfsfunktionen zur Darstellung der Ergebnisse als Plot
def make_meshgrid(x, y, h=.02):
    x_min, x_max = x.min() - 1, x.max() + 1
    y_min, y_max = y.min() - 1, y.max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    return xx, yy


def plot_contours(ax, clf, xx, yy, **params):
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    out = ax.contourf(xx, yy, Z, **params)
    return out


# wende lineare SVM auf den Datensatz an
model = svm.SVC(kernel='linear')
clf = model.fit(X_resampled, y_resampled)


# stelle Resultate grafisch dar
fig, ax = plt.subplots()
X0, X1 = X_resampled[:, 0], X_resampled[:, 1]
xx, yy = make_meshgrid(X0, X1)

plot_contours(ax, clf, xx, yy, cmap=plt.cm.coolwarm, alpha=0.8)
ax.scatter(X0, X1, c=y_resampled, cmap=plt.cm.coolwarm, s=20, edgecolors='k')
ax.set_ylabel('Feature 1')
ax.set_xlabel('Feature 2')
ax.set_xticks(())
ax.set_yticks(())
ax.set_title('Linear SVC with InstanceHardnessThreshold')
ax.legend()
plt.show()
Machine-Learning mit unausgewogenen Datensätzen imbalanced-learn (Python-Package) Bild1
Machine-Learning mit unausgewogenen Datensätzen imbalanced-learn (Python-Package) Bild6
Machine-Learning mit unausgewogenen Datensätzen imbalanced-learn (Python-Package) Bild5
Machine-Learning mit unausgewogenen Datensätzen imbalanced-learn (Python-Package) Bild7

Erneut ist erkenn­bar, dass die Wahl der Under-Sam­pling Methode einen signi­fi­kan­ten Ein­fluss auf das End­ergeb­nis hat. Dar­über hin­aus fällt auf, dass die­ses Mal auch wei­tere Para­me­ter an die Methode Ins­tance­Hard­ness­Th­res­hold über­ge­ben wur­den. Dazu spä­ter mehr.

Kom­bi­na­tion von Under- und Over-Sampling

Das imba­lan­ced-learn Paket bie­tet zwei Kom­bi­na­tio­nen von Under- und Over-Sam­pling Metho­den an:

  • SMOTE + Tomek links
  • SMOTE + Edi­ted Nea­rest Neighbours

Der bekannte Test­da­ten­satz mit 2 Fea­tures wird ver­wen­det, um die Unter­schiede der bei­den Metho­den zu ver­an­schau­li­chen. Erneut kommt exem­pla­risch der Line­rae SVM Klas­si­fi­zie­rer zum Einsatz.

import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_classification
from imblearn.combine import SMOTETomek

# erstelle unbalancierten Testdatensatz mit 2 Features
X, y = make_classification(n_samples=5000, n_features=2, n_informative=2,
                           n_redundant=0, n_repeated=0, n_classes=3,
                           n_clusters_per_class=1,
                           weights=[0.01, 0.05, 0.94],
                           class_sep=0.8, random_state=0)

# verwende den SMOTETomek aus dem imblearn Paket, um den Datensatz zu balancieren
smotetomek = SMOTETomek()
X_resampled, y_resampled = smotetomek.fit_resample(X, y)


# Hilfsfunktionen zur Darstellung der Ergebnisse als Plot
def make_meshgrid(x, y, h=.02):
    x_min, x_max = x.min() - 1, x.max() + 1
    y_min, y_max = y.min() - 1, y.max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    return xx, yy


def plot_contours(ax, clf, xx, yy, **params):
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    out = ax.contourf(xx, yy, Z, **params)
    return out


# wende lineare SVM auf den Datensatz an
model = svm.SVC(kernel='linear')
clf = model.fit(X_resampled, y_resampled)


# stelle Resultate grafisch dar
fig, ax = plt.subplots()
X0, X1 = X_resampled[:, 0], X_resampled[:, 1]
xx, yy = make_meshgrid(X0, X1)

plot_contours(ax, clf, xx, yy, cmap=plt.cm.coolwarm, alpha=0.8)
ax.scatter(X0, X1, c=y_resampled, cmap=plt.cm.coolwarm, s=20, edgecolors='k')
ax.set_ylabel('Feature 1')
ax.set_xlabel('Feature 2')
ax.set_xticks(())
ax.set_yticks(())
ax.set_title('Linear SVC with SMOTETomek')
ax.legend()
plt.show()
Machine-Learning mit unausgewogenen Datensätzen imbalanced-learn (Python-Package) Bild1
Machine-Learning mit unausgewogenen Datensätzen imbalanced-learn (Python-Package) Bild9
Machine-Learning mit unausgewogenen Datensätzen imbalanced-learn (Python-Package) Bild8

Erneut sind deut­li­che Unter­schiede in der Her­an­ge­hens­weise der Metho­den erkenn­bar, wäh­rend der Auf­ruf der Metho­den iden­tisch ist. 

Spe­zi­fi­zie­rung der Methoden

Es wurde bereits bei der Ver­wen­dung der Ins­tance­Hard­ness­Th­res­hold Under-Sam­pling Methode dar­auf hin­ge­wie­sen, dass den Metho­den wei­tere Para­me­ter über­ge­ben wer­den kön­nen. In der Tat bie­ten alle Metho­den die Mög­lich­keit, wei­ter spe­zi­fi­ziert zu wer­den, um den gewünsch­ten Anfor­de­run­gen gerecht zu wer­den. Wenn­gleich an die­ser Stelle nicht alle Metho­den mit ihren Para­me­tern prä­sen­tiert wer­den kön­nen, soll doch anhand eines Bei­spiels auf­ge­zeigt wer­den, wel­che Mög­lich­kei­ten das imba­lan­ced-learn Paket offe­riert. Betrachte dazu die Ins­tance­Hard­ness­Th­res­hold Methode:

Ins­tance­Hard­ness­Th­res­hold(estimator=None, sampling_strategy=’auto’,  return_indices=False, random_state=None, cv=5, n_jobs=1, ratio=None)

Je nach Situa­tion las­sen sich etwa ver­schie­dene Klas­si­fi­zie­rer (esti­ma­tor) zur Schät­zung der ins­tance hard­ness der Stich­probe wäh­len, zum Bei­spiel durch Über­gabe eines Strings ‘knn‘, ‘decis­ion-tree‘, ‘ran­dom-forest‘, ‘ada­boost‘, ‘gra­di­ent-boos­ting‘ oder ‘linear-svm‘ oder durch Über­gabe eines Objek­tes wie im obi­gen Bei­spiel etwa sklearn.linear_model.LogisticRegression.

Die Vor­ge­hens­weise des Sam­plings kann durch die sampling_strategy gesteu­ert wer­den. So wird durch Über­gabe des Strings ‘not majo­rity‘ spe­zi­fi­ziert, dass alle Klasse bis auf die Mehr­heits­klasse resam­pled wer­den. Durch random_state lässt sich ein Seed spe­zi­fi­zie­ren, wel­cher die Ran­do­mi­sie­rung des Algo­rith­mus steu­ert. Mit n_jobs lässt sich spe­zi­fi­zie­ren, auf wie viele Threads die Berech­nung ver­teilt wer­den soll, sofern möglich.

Für eine detail­lierte Doku­men­ta­tion aller Metho­den bie­tet sich die eigene Web­site des imba­lan­ced-learn Pakets an, wel­che zusätz­lich mit diver­sen Bei­spie­len zur Anwen­dung auf­war­tet, wel­che über die hier dar­ge­stell­ten Bei­spiele hin­aus­ge­hen. Nichts­des­to­trotz dürfte die­ses Bei­spiel einen guten Ein­blick in die Varia­bi­li­tät und Viel­sei­tig­keit geben, die durch das imba­lan­ced-learn Paket ange­bo­ten wird.

Fazit

Das imba­lan­ced-learn Paket bie­tet genau das, was der Name nahe­legt: eine Viel­zahl von ver­schie­de­nen Metho­den, um unaus­ge­wo­gene Daten­sätze zu balan­cie­ren. Die Metho­den decken die gän­gi­gen Vari­an­ten für diese Pro­blem­stel­lung ab und kön­nen durch eine ein­fa­che Anwend­bar­keit über­zeu­gen. Durch die Angabe wei­te­rer Para­me­ter las­sen sich die ver­schie­de­nen Metho­den auf unter­schied­li­che Anfor­de­run­gen zuschnei­den, was die Ver­wend­bar­keit uni­ver­sell macht. Zudem punk­tet die Web­site des imba­lan­ced-learn Pakets neben einer über­sicht­li­chen Doku­men­ta­tion der Metho­den mit diver­sen Anwen­dungs- und Code-Beispielen.

Wer mit unaus­ge­wo­ge­nen Daten kon­fron­tiert ist und einen Bezug zu Python hat, sollte dem imba­lan­ced-learn Paket auf jeden Fall einen Blick wid­men und die Funk­tio­na­li­tä­ten aus­tes­ten – die Zugangs­hür­den sind gering und die Ergeb­nisse überzeugend.