Ich prĂ€sentiere Ihnen den zweiten Teil des Artikels ĂŒber die Suche nach mutmaĂlichem Betrug basierend auf Daten aus Enron Dataset. Wenn Sie den ersten Teil nicht gelesen haben, können Sie sich hier damit vertraut machen.
Jetzt werden wir ĂŒber den Prozess des Aufbaus, Optimierens und AuswĂ€hlens eines Modells sprechen, das die Antwort gibt: Lohnt es sich, eine Person des Betrugs zu verdĂ€chtigen?
Zuvor haben wir einen der offenen DatensĂ€tze analysiert, der Informationen zu VerdĂ€chtigen im Fall Enron und Betrug darin enthĂ€lt. AuĂerdem wurde die Verzerrung in den Anfangsdaten korrigiert, LĂŒcken (NaN) wurden gefĂŒllt, wonach die Daten normalisiert und die Auswahl der Attribute abgeschlossen wurde.
Das Ergebnis war vielen bekannt:
- X_train und y_train - die fĂŒr das Training verwendete Stichprobe (111 DatensĂ€tze);
- X_test und y_test - ein Beispiel, anhand dessen die Richtigkeit der Vorhersagen unserer Modelle ĂŒberprĂŒft wird (28 EintrĂ€ge).
Apropos Modelle ... Um anhand einiger Anzeichen, die seine AktivitĂ€ten charakterisieren, richtig vorherzusagen, ob es sich lohnt, eine Person zu verdĂ€chtigen, verwenden wir die Klassifizierung. Die wichtigsten Modelltypen zur Lösung von Problemen in diesem Segment können von Sklearn ĂŒbernommen werden:
- Naive Bayes (naiver Bayes-Klassifikator);
- SVM (Referenzvektormaschine);
- K-nÀchste Nachbarn (Methode zum Finden der nÀchsten Nachbarn);
- ZufÀlliger Wald (zufÀlliger Wald);
- Neuronales Netz.
Es gibt auch ein Bild, das ihre Anwendbarkeit recht gut veranschaulicht:
Unter ihnen gibt es einen Entscheidungsbaum (Entscheidungsbaum), der vielen bekannt ist, aber vielleicht macht es in einer Aufgabe keinen Sinn, diese Methode zusammen mit Random Forest zu verwenden, einem Ensemble von EntscheidungsbÀumen. Ersetzen Sie es daher durch die logistische Regression, die als Klassifizierer fungieren und eine der erwarteten Optionen (0 oder 1) erzeugen kann.
Starten Sie
Wir initialisieren alle genannten Klassifikatoren mit Standardwerten:
from sklearn.naive_bayes import GaussianNB from sklearn.linear_model import LogisticRegression from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC from sklearn.neural_network import MLPClassifier from sklearn.ensemble import RandomForestClassifier random_state = 42 gnb = GaussianNB() svc = SVC() knn = KNeighborsClassifier() log = LogisticRegression(random_state=random_state) rfc = RandomForestClassifier(random_state=random_state) mlp = MLPClassifier(random_state=random_state)
Wir werden sie auch so gruppieren, dass es bequemer ist, mit ihnen als Aggregat zu arbeiten, als Code fĂŒr jede Person zu schreiben. Zum Beispiel können wir sie alle auf einmal trainieren:
classifiers = [gnb, svc, knn, log, rfc, mlp] for clf in classifiers: clf.fit(X_train, y_train)
Nachdem die Modelle trainiert worden waren, war es Zeit fĂŒr den ersten Test ihrer VorhersagequalitĂ€t. ZusĂ€tzlich visualisieren wir unsere Ergebnisse mit Seaborn:
from sklearn.metrics import accuracy_score def calculate_accuracy(X, y): result = pd.DataFrame(columns=['classifier', 'accuracy']) for clf in classifiers: predicted = clf.predict(X_test) accuracy = round(100.0 * accuracy_score(y_test, predicted), 2) classifier = clf.__class__.__name__ classifier = classifier.replace('Classifier', '') result = result.append({'classifier': classifier, 'accuracy': accuracy}, ignore_index=True) print('Accuracy is {accuracy}% for {classifier_name}'.format(accuracy=accuracy, classifier_name=classifier)) result = result.sort_values(['classifier'], ascending=True) plt.subplots(figsize=(10, 7)) sns.barplot(x="classifier", y='accuracy', palette=cmap, data=result)
Werfen wir einen Blick auf die allgemeine Vorstellung von der Genauigkeit der Klassifikatoren:
calculate_accuracy(X_train, y_train)

Auf den ersten Blick sieht es ziemlich gut aus, die Genauigkeit der Vorhersagen auf der Testprobe schwankt um 90%. Es scheint, dass die Aufgabe brillant ist!
In der Tat ist nicht alles so rosig.Eine hohe Genauigkeit ist keine Garantie fĂŒr korrekte Vorhersagen. Unsere Testprobe enthĂ€lt 28 Aufzeichnungen, von denen 4 VerdĂ€chtige und 24 VerdĂ€chtige betreffen. Stellen Sie sich vor, wir hĂ€tten eine Art Algorithmus der Form erstellt:
def QuaziAlgo(features): return 0
Dann gaben sie ihm unser Testmuster am Eingang und sie erhielten, dass alle 28 Personen unschuldig waren. Wie genau wird der Algorithmus in diesem Fall sein?
Interessanterweise hat KNeighbors die gleiche Vorhersagegenauigkeit ...
Bevor wir uns jedoch schmeicheln, wollen wir eine Verwirrungsmatrix fĂŒr die Vorhersageergebnisse erstellen:
from sklearn.metrics import confusion_matrix def make_confussion_matrices(X, y): matrices = {} result = pd.DataFrame(columns=['classifier', 'recall']) for clf in classifiers: classifier = clf.__class__.__name__ classifier = classifier.replace('Classifier', '') predicted = clf.predict(X_test) print(f'{predicted}-{classifier}') matrix = confusion_matrix(y_test,predicted,labels=[1,0]) matrices[classifier] = matrix.T return matrices
Wir berechnen die Fehlermatrizen fĂŒr jeden Klassifikator und sehen zusammen mit diesen, was sie vorhergesagt haben:
matrices = make_confussion_matrices(X_train,y_train)

Selbst eine textuelle Darstellung des Ergebnisses der Arbeit der Klassifikatoren reicht aus, um zu verstehen, dass eindeutig etwas schief gelaufen ist.
Die Methode der nĂ€chsten Nachbarn ergab ĂŒberhaupt keinen einzigen VerdĂ€chtigen in der Testprobe. Es stellen sich zwei Fragen:
- Was ist der Grund fĂŒr dieses Verhalten des KNeighbors-Klassifikators?
- Warum haben wir Fehlermatrizen erstellt, wenn wir sie nicht verwenden, sondern nur die Ergebnisse der Vorhersage betrachten?
Schau genauer hin
Beginnen wir mit der zweiten Frage. Versuchen wir, unsere Fehlermatrizen zu visualisieren und die Daten in grafischem Format darzustellen, um zu verstehen, wo der Klassifizierungsfehler auftritt:
import itertools from collections import Iterable def draw_confussion_matrices(row,col,matrices,figsize = (16,12)): fig, (axes) = plt.subplots(row,col, sharex='col', sharey='row',figsize=figsize ) if any(isinstance(i, Iterable) for i in axes): axes = list(itertools.chain.from_iterable(axes)) idx = 0 for name,matrix in matrices.items(): df_cm = pd.DataFrame( matrix, index=['True','False'], columns=['True','False'], ) ax = axes[idx] fig.subplots_adjust(wspace=0.1) sns.heatmap(df_cm, annot=True,cmap=cmap,cbar=False ,fmt="d",ax=ax,linewidths=1) ax.set_title(name) idx += 1
Wir zeigen sie in 2 Zeilen und 3 Spalten an:
draw_confussion_matrices(2,3,matrices)

Bevor Sie fortfahren, sollten Sie einige ErlĂ€uterungen geben. Die Bezeichnung True, die sich links von der Fehlermatrix eines bestimmten Klassifikators befindet, bedeutet, dass der Klassifikator die Person als verdĂ€chtig angesehen hat. Der Wert False bedeutet, dass die Person nicht verdĂ€chtig ist. In Ă€hnlicher Weise geben Richtig und Falsch am unteren Rand des Bildes einen realen Zustand an, der möglicherweise nicht mit der Entscheidung des Klassifikators ĂŒbereinstimmt.
Zum Beispiel sehen wir, dass KNeighbors Entscheidungen mit einer Vorhersagegenauigkeit von 85,71% mit der tatsĂ€chlichen Situation ĂŒbereinstimmten, als 24 Personen, die keinen Verdacht hatten, vom Klassifikator in eine Ă€hnliche Liste aufgenommen wurden. Aber auch 4 Personen aus der Liste der VerdĂ€chtigen wurden in diese Liste aufgenommen. Wenn dieser Klassifikator Entscheidungen getroffen hĂ€tte, hĂ€tte vielleicht jemand das Gericht meiden können.
Daher sind Fehlermatrizen ein sehr gutes Werkzeug, um zu verstehen, was bei Klassifizierungsproblemen schief gelaufen ist. Ihr Hauptvorteil ist die Sichtbarkeit, und deshalb appellieren wir an sie.
Metriken
Im Allgemeinen kann dies durch das folgende Bild veranschaulicht werden:

Und was ist in diesem Fall TP, TN, FP und eine Art FN?
Mit anderen Worten, wir bemĂŒhen uns sicherzustellen, dass die Antworten des Klassifikators und der tatsĂ€chliche Sachverhalt ĂŒbereinstimmen. Das heiĂt, um sicherzustellen, dass alle Zahlen zwischen den Zellen TP und TN (echte Lösungen) verteilt sind und nicht in FN und FP (falsche Lösungen) fallen.
Nicht immer ist alles so dramatisch und eindeutigIm kanonischen Fall mit Krebsdiagnose ist FP beispielsweise FN vorzuziehen, da dem Patienten im Falle eines falschen Urteils ĂŒber Krebs Medikamente verschrieben und behandelt werden. Ja, es wird seine Gesundheit und seinen Geldbeutel beeintrĂ€chtigen, aber es wird immer noch als weniger gefĂ€hrlich angesehen als FN und die versĂ€umte Zeit, in der Krebs mit kleinen Mitteln besiegt werden kann.
Was ist mit den VerdÀchtigen in unserem Fall? FN ist wahrscheinlich nicht so schlecht wie FP. Aber dazu spÀter mehr ...
Und da es sich um AbkĂŒrzungen handelt, ist es an der Zeit, sich an die Metriken Genauigkeit (PrĂ€zision) und VollstĂ€ndigkeit (RĂŒckruf) zu erinnern.
Wenn Sie von der formalen Aufzeichnung abweichen, kann PrĂ€zision ausgedrĂŒckt werden als:

Mit anderen Worten, es wird ein Konto darĂŒber gefĂŒhrt, wie viele positive Antworten vom Klassifizierer korrekt sind. Je höher die Genauigkeit, desto geringer die Anzahl der Fehlertreffer (die Genauigkeit betrĂ€gt 1, wenn keine FPs vorhanden sind).
RĂŒckruf wird allgemein dargestellt als:

RĂŒckruf kennzeichnet die FĂ€higkeit des Klassifikators, so viele positive Antworten wie möglich zu âerratenâ. Je höher die VollstĂ€ndigkeit, desto niedriger die FN.
Normalerweise versuchen sie, ein Gleichgewicht zwischen beiden herzustellen, aber in diesem Fall wird der PrÀzision die PrioritÀt vollstÀndig eingerÀumt. Der Grund: ein humanistischerer Ansatz, der Wunsch, die Anzahl der Fehlalarme zu minimieren und infolgedessen zu vermeiden, dass der Verdacht auf die Unschuldigen fÀllt.
Wir berechnen die Genauigkeit fĂŒr unsere Klassifikatoren:
from sklearn.metrics import precision_score def calculate_precision(X, y): result = pd.DataFrame(columns=['classifier', 'precision']) for clf in classifiers: predicted = clf.predict(X_test) precision = precision_score(y_test, predicted, average='macro') classifier = clf.__class__.__name__ classifier = classifier.replace('Classifier', '') result = result.append({'classifier': classifier, 'precision': precision}, ignore_index=True) print('Precision is {precision} for {classifier_name}'.format(precision=round(precision,2), classifier_name=classifier)) result = result.sort_values(['classifier'], ascending=True) plt.subplots(figsize=(10, 7)) sns.barplot(x="classifier", y='precision', palette=cmap, data=result) calculate_precision(X_train, y_train)

Wie aus der Abbildung hervorgeht, stellte sich heraus, dass die Genauigkeit von KNeighbors am niedrigsten war, da der TP-Wert am niedrigsten ist.
Gleichzeitig gibt es einen guten Artikel ĂŒber Metriken zum HabrĂ©, und diejenigen, die tiefer in dieses Thema eintauchen möchten, sollten sich damit vertraut machen.
Hyperparameterauswahl
Nachdem wir die Metrik gefunden haben, die den ausgewĂ€hlten Bedingungen am besten entspricht (wir reduzieren die Anzahl der FPs), können wir zur ersten Frage zurĂŒckkehren: Was ist der Grund fĂŒr dieses Verhalten des KNeighbors-Klassifikators?
Der Grund liegt in den Standardeinstellungen, mit denen dieses Modell erstellt wurde. Und höchstwahrscheinlich könnten viele zu diesem Zeitpunkt ausrufen: Warum mit Standardparametern trainieren? Es gibt spezielle Auswahlwerkzeuge, zum Beispiel das hÀufig verwendete GridSearchCV.
Ja, das ist es, und es ist an der Zeit, darauf zurĂŒckzugreifen.
Aber vorher entfernen wir den Bayes'schen Klassifikator von unserer Liste. Es erlaubt eine FP, und gleichzeitig akzeptiert dieser Algorithmus keine variablen Parameter, wodurch sich das Ergebnis nicht Àndert.
classifiers.remove(gnb)
Feinabstimmung
Wir definieren ein Parameterraster fĂŒr jeden Klassifikator:
parameters = {'SVC':{'kernel':('linear', 'rbf','poly'), 'C':[i for i in range(1,11)],'random_state': (random_state,)}, 'KNeighbors':{'algorithm':('ball_tree', 'kd_tree'), 'n_neighbors':[i for i in range(2,20)]}, 'LogisticRegression':{'penalty':('l1', 'l2'), 'C':[i for i in range(1,11)],'random_state': (random_state,)}, 'RandomForest':{'n_estimators':[i for i in range(10,101,10)],'random_state': (random_state,)}, 'MLP':{'activation':('relu','logistic'),'solver':('sgd','lbfgs'),'max_iter':(500,1000), 'hidden_layer_sizes':[(7,),(7,7)],'random_state': (random_state,)}}
AuĂerdem wollte ich auf die Anzahl der Schichten / Neuronen in MLP achten.
Es wurde beschlossen, sie nicht durch erschöpfende Suche aller möglichen Werte festzulegen, sondern dennoch auf der Formel zu basieren:
Ich möchte sofort sagen, dass Training und Kreuzvalidierung nur fĂŒr das Trainingsmuster durchgefĂŒhrt werden. Ich gehe davon aus, dass es eine Meinung gibt, dass Sie dies fĂŒr alle Daten tun können, wie im Beispiel mit Iris Dataset. Meiner Meinung nach ist dieser Ansatz jedoch nicht ganz gerechtfertigt, da es nicht möglich sein wird, den Ergebnissen der ĂberprĂŒfung an einer Testprobe zu vertrauen.
Wir werden die Optimierung durchfĂŒhren und unsere Klassifikatoren durch ihre verbesserte Version ersetzen:
from sklearn.model_selection import GridSearchCV warnings.filterwarnings('ignore') for idx,clf in enumerate(classifiers): classifier = clf.__class__.__name__ classifier = classifier.replace('Classifier', '') params = parameters.get(classifier) if not params: continue new_clf = clf.__class__() gs = GridSearchCV(new_clf, params, cv=5) result =gs.fit(X_train, y_train) print(f'The best params for {classifier} are {result.best_params_}') classifiers[idx] = result.best_estimator_

Nachdem wir eine Metrik fĂŒr die Bewertung ausgewĂ€hlt und GridSearchCV durchgefĂŒhrt haben, können wir die letzte Linie ziehen.
Zusammenfassend
Fehlermatrix v.2
matrices = make_confussion_matrices(X_train,y_train) draw_confussion_matrices(1,2,first_row,figsize = (10.5,6)) draw_confussion_matrices(1,3,second_row,figsize = (16,6))


Wie aus der Matrix ersichtlich ist, zeigte MLP einen Abbau und ging davon aus, dass die Testprobe keine VerdĂ€chtigen enthielt. Random Forest gewann an Genauigkeit und korrigierte die Parameter fĂŒr False Negative und True Positive. Und KNeighbors zeigte eine Verbesserung der Vorhersage. Die Prognose fĂŒr andere hat sich nicht geĂ€ndert.
Genauigkeit v.2
Jetzt hat keiner unserer aktuellen Klassifikatoren Fehler mit False Positive, was eine gute Nachricht ist. Wenn wir jedoch alles in der Sprache der Zahlen ausdrĂŒcken, erhalten wir das folgende Bild:
calculate_precision(X_train, y_train)


Es wurden 3 Klassifikatoren mit der höchsten PrÀzisionsbewertung identifiziert. Und sie haben die gleichen Werte, basierend auf der Fehlermatrix. Welcher Klassifikator soll gewÀhlt werden?
Wer ist besser
Es scheint mir, dass dies eine ziemlich schwierige Frage ist, auf die es keine universelle Antwort gibt. Mein Standpunkt in diesem Fall wĂŒrde jedoch ungefĂ€hr so ââaussehen:
1. Der Klassifikator sollte in seiner technischen Implementierung so einfach wie möglich sein. Dann hat er ein geringeres Risiko fĂŒr eine Umschulung (wahrscheinlich ist dies bei MLP passiert). Daher ist dies kein zufĂ€lliger Wald, da dieser Algorithmus ein Ensemble von 30 BĂ€umen ist und daher von diesen abhĂ€ngt. Im Einklang mit einer der Ideen von Python Zen: Einfach ist besser als komplex.
2. Nicht schlecht, wenn der Algorithmus intuitiv war. Das heiĂt, KNeighbors wird einfacher wahrgenommen als SVMs mit potenziellem mehrdimensionalem Raum.
Was wiederum einer anderen Aussage Àhnelt: explizit ist besser als implizit.
Daher ist KNeighbors mit 3 Nachbarn meiner Meinung nach der beste Kandidat.
Dies ist das Ende des zweiten Teils, in dem die Verwendung des Enron-Datensatzes als Beispiel fĂŒr die Klassifizierungsaufgabe beim maschinellen Lernen beschrieben wird. Basierend auf den Materialien aus dem Kurs EinfĂŒhrung in maschinelles Lernen ĂŒber Udacity. Es gibt auch ein Python-Notizbuch , das die gesamte beschriebene Abfolge von Aktionen widerspiegelt.