# -*- coding: utf-8 -*- """ Binary classification ===================== A binary classification problem is to find a frontier to separate two clouds of points (see `binary classification `_). .. contents:: :local: .. index:: classification, binary, ROC """ ########################### # # What does it solve? # ------------------- # # We start by generating an artificial cloud of points. from sklearn.datasets import make_classification X, Y = make_classification(n_samples=500, n_features=2, n_classes=2, n_repeated=0, n_redundant=0) ########################### # We plot the data. import matplotlib.pyplot as plt fig = plt.figure(figsize=(5, 5)) ax = plt.subplot() ax.plot(X[Y == 0, 0], X[Y == 0, 1], "ob") ax.plot(X[Y == 1, 0], X[Y == 1, 1], "or") ######################## # We represent the data in dataframe. import pandas data = pandas.DataFrame(data=X, columns=["X1", "X2"]) data["Label"] = Y.astype(float) ########################## # # From a geometrical point of view, a binary classification # problem consists in finding the best boundary between # two clouds of points. The simplest is to assume that it is # a straight line. In this case, a logistic regression model # will help us. from microsoftml import rx_logistic_regression, rx_predict logreg = rx_logistic_regression("Label ~ X1 + X2", data=data) ############################## # The model produces a line boundary # whose coefficients are: print(logreg.coef_) ############################### # We could trace this line but this graph # would only be valid for a linear model. # Instead we color the background of the graph with # the color of the class predicted by the model. import numpy def colorie(X, model, ax, fig): if isinstance(X, pandas.DataFrame): X = X.as_matrix() xmin, xmax = numpy.min(X[:, 0]), numpy.max(X[:, 0]) ymin, ymax = numpy.min(X[:, 1]), numpy.max(X[:, 1]) hx = (xmax - xmin) / 100 hy = (ymax - ymin) / 100 xx, yy = numpy.mgrid[xmin:xmax:hx, ymin:ymax:hy] grid = numpy.c_[xx.ravel(), yy.ravel()] dfgrid = pandas.DataFrame(data=grid, columns=["X1", "X2"]) probs = rx_predict(model, dfgrid).as_matrix()[:, 1].reshape(xx.shape) contour = ax.contourf(xx, yy, probs, 25, cmap="RdBu", vmin=0, vmax=1) ax_c = fig.colorbar(contour) ax_c.set_label("$P(y = 1)$") ax_c.set_ticks([0, .25, .5, .75, 1]) ax.set_xlim([xmin, xmax]) ax.set_ylim([ymin, ymax]) fig = plt.figure(figsize=(7, 5)) ax = plt.subplot() colorie(X, logreg, ax, fig) ax.scatter(X[:, 0], X[:, 1], c=Y, s=50, cmap="RdBu", vmin=-.2, vmax=1.2, edgecolor="white", linewidth=1) ax.set(aspect="equal", xlabel="$X_1$", ylabel="$X_2$") ############################## # # Evaluation # ---------- # # There are two tests that are carried out in a quasi-systematic way # for this type of problem: one :epkg:`confusion matrix` # And a curve :epkg:`ROC`. # # The first step is to divide the dataset into # training set (train) and test set (test). # This is necessary because some models can simply # learn by heart reach a null error on the data # used to learn. This is the case with the # `nearest neighbors ` _. # The division must be random. Moreover, # this can be repeated several times # to ensure the robustness of the results. try: from sklearn.model_selection import train_test_split except ImportError: from sklearn.cross_validation import train_test_split data_train, data_test = train_test_split(data) ##################### # We train on the training set. logreg = rx_logistic_regression("Label ~ X1 + X2", data=data_train) ##################### # We predict on the test set. y_pred = rx_predict(logreg, data=data_test) ########################## # We compute the :epkg:`confusion matrix`. from sklearn.metrics import confusion_matrix conf = confusion_matrix(data_test["Label"], y_pred["PredictedLabel"]) print(conf) ########################## # The numbers on the diagonal indicate the observations # ranked in the right class. Elsewhere, these are the miclassified observations # These are blue dots on a red background for example. fig = plt.figure(figsize=(7, 5)) ax = plt.subplot() colorie(data_test[["X1", "X2"]], logreg, ax, fig) ax.scatter(data_test["X1"], data_test["X2"], c=data_test["Label"], s=50, cmap="RdBu", vmin=-.2, vmax=1.2, edgecolor="white", linewidth=1) ax.set(aspect="equal", xlabel="$X_1$", ylabel="$X_2$") ax.set_title("Résultats sur la base de test") ######################## # Some points are more or less far from the boundary # the model found between the two classes. The more # a point is far from the border, the more the model is sure of its class. # This is true for logistic regression but also for most # of the models used to make a binary classification. # This is how the model is able to give an relevance indicator: # the distance between the point and the boundary the model found. # This distance is converted into a score or a probability. # The :epkg:`confusion matrix` does not use this information, # The curve :epkg:`ROC` does. Let's see how. # This is inspired by the example # `Receiver Operating Characteristic (ROC) ` _. # The curve :epkg:`ROC` is the same with scores or probabilities. print(y_pred[:5]) ########################### # For a binary classification, :epkg:`microsoftml` returns # the predicted label, a score and the probability of each # observation to be in class 1. # We build the :epkg:`ROC` curve. # The model is right if it finds the right class. # with is translated by ``y_pred["PredictedLabel"] == data_test["Label"]``. # The score is ``y_pred["Probability"]`` when the predicted class is 1, # ``1 - y_pred["Probability"]`` otherwise. from sklearn.metrics import roc_curve prob_pred = [y_pred.loc[i, "Probability"] if y_pred.loc[i, "PredictedLabel"] \ else (1 - y_pred.loc[i, "Probability"]) for i in range(0, y_pred.shape[0])] good = y_pred["PredictedLabel"].as_matrix() == (data_test["Label"] == 1).as_matrix() fpr, tpr, th = roc_curve(good.ravel(), prob_pred) ################################ # For index ``i``, ``th[i]`` is a threshold, # ``tpr[i]`` is the ratio of correclty classified observations # with a probability higher than``th[i]``. # ``fpr[i]`` is the ratio of misclassified observations # with a probability higher than``th[i]``. print("i=2", th[2], tpr[2], fpr[2]) print("i={0}".format(len(th) - 2), th[-2], tpr[-2], fpr[-2]) ############################## # The lower the score is, the higher the proportion is. # When the threshold is very low, the model returns a score or a probability # always above the threshold and the two proportions are equal to 1. # If the threshold is high, the two proportions are null. # We draw the curve by using many distinct thresholds. plt.figure() lw = 2 plt.plot(fpr, tpr, color='darkorange', lw=lw, label='ROC Curve') plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--') plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel("False Positive Rate") plt.ylabel("True Positive Rate") plt.title('ROC') plt.legend(loc="lower right") ##################### # The area under the curve or :epkg:`AUC` (Area Under the Curve) # is related to the relevance of the model. # The higher the score is, the larger the proportion of correclty classified # observations is compared to the proportion of misclassified examples. # When these proportions are always equal whatever the threshold is, # the curve fits the first diagonal and the model # has learned nothing. The higher the curve is, the better the model is. # This is why the area under the curve is calculated. from sklearn.metrics import auc print(auc(fpr, tpr)) ##################################### # The curve :epkg:`ROC` always applies to a binary decision. # The binary classification can be turned into three binary decisions: # # * The model correclty classified an example, either in class 0 or class 1. # * The model correclty classified an example in class 0. # * The model correclty classified an example in class 1. # # If the answers are linked, the model usually gives # different performance on these three issues. # We compute the curves :epkg:`ROC` related to these three questions. fpr_cl = dict() tpr_cl = dict() fpr_cl["classe 0"], tpr_cl["classe 0"], _ = roc_curve( 1 - data_test["Label"], 1 - y_pred["Probability"]) fpr_cl["classe 1"], tpr_cl["classe 1"], _ = roc_curve( data_test["Label"], y_pred["Probability"]) # y_test == 1 fpr_cl["all"], tpr_cl["all"] = fpr, tpr # We already computed this. ############################## # We plot. plt.figure() for key in fpr_cl: plt.plot(fpr_cl[key], tpr_cl[key], label=key) plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--') plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel("False Positive Rate") plt.ylabel("True Positive Rate") plt.title('ROC(s)') plt.legend(loc="lower right")