.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/ml/plot_roc.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_ml_plot_roc.py: .. _l-ml-plot-roc: Receiver Operating Characteristic (ROC) ======================================= Un problème de classification binaire consiste à trouver un moyen de séparer deux nuages de points (voir `classification `_) et on évalue le plus souvent sa pertinence à l'aide d'une courbe :epkg:`ROC`. Cet exemple montre différente représentation de la même information. Classification binaire ---------------------- On commence par générer un nuage de points artificiel. .. GENERATED FROM PYTHON SOURCE LINES 20-42 .. code-block:: Python import numpy from sklearn.metrics import ( f1_score, precision_recall_curve, roc_curve, confusion_matrix, ) from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.datasets import make_classification import matplotlib.pyplot as plt from teachpyx.ext_test_case import unit_test_going X, Y = make_classification( n_samples=10000 if unit_test_going() else 100, n_features=2, n_classes=2, n_repeated=0, n_redundant=0, ) .. GENERATED FROM PYTHON SOURCE LINES 43-44 On représente ces données. .. GENERATED FROM PYTHON SOURCE LINES 44-50 .. code-block:: Python fig = plt.figure(figsize=(5, 5)) ax = plt.subplot() ax.plot(X[Y == 0, 0], X[Y == 0, 1], ".b") ax.plot(X[Y == 1, 0], X[Y == 1, 1], ".r") .. image-sg:: /auto_examples/ml/images/sphx_glr_plot_roc_001.png :alt: plot roc :srcset: /auto_examples/ml/images/sphx_glr_plot_roc_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none [] .. GENERATED FROM PYTHON SOURCE LINES 51-52 On découpe en train / test. .. GENERATED FROM PYTHON SOURCE LINES 52-54 .. code-block:: Python X_train, X_test, y_train, y_test = train_test_split(X, Y) .. GENERATED FROM PYTHON SOURCE LINES 55-56 On apprend sur la base d'apprentissage. .. GENERATED FROM PYTHON SOURCE LINES 56-60 .. code-block:: Python logreg = LogisticRegression() logreg.fit(X_train, y_train) .. raw:: html
LogisticRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.


.. GENERATED FROM PYTHON SOURCE LINES 61-62 Et on prédit sur la base de test. .. GENERATED FROM PYTHON SOURCE LINES 62-64 .. code-block:: Python y_pred = logreg.predict(X_test) .. GENERATED FROM PYTHON SOURCE LINES 65-66 On calcule la :epkg:`matrice de confusion`. .. GENERATED FROM PYTHON SOURCE LINES 66-70 .. code-block:: Python conf = confusion_matrix(y_test, y_pred) print(conf) .. rst-class:: sphx-glr-script-out .. code-block:: none [[12 0] [ 3 10]] .. GENERATED FROM PYTHON SOURCE LINES 71-93 Trois courbes ------------- La courbe :epkg:`ROC` s'applique toujours à un problème de classification binaire qu'on peut scinder en trois questions : * Le modèle a bien classé un exemple dans la classe 0. * Le modèle a bien classé un exemple dans la classe 1. * Le modèle a bien classé un exemple, que ce soit dans la classe 0 ou la classe 1. Ce problème suppose implicitement que le même seuil est utilisé sur chacun des classes. C'est-à-dire qu'on prédit la classe 1 si le score pour la classe 1 est supérieur à à celui obtenu pour la classe 0 mais aussi qu'on valide la réponse si le score de la classe 1 ou celui de la classe 0 est supérieur au même seuil *s*, ce qui n'est pas nécessairement le meilleur choix. Si les réponses sont liées, le modèle peut répondre de manière plus ou moins efficace à ces trois questions. On calcule les courbes :epkg:`ROC` à ces trois questions. .. GENERATED FROM PYTHON SOURCE LINES 93-111 .. code-block:: Python fpr_cl = dict() tpr_cl = dict() y_pred = logreg.predict(X_test) y_proba = logreg.predict_proba(X_test) fpr_cl["classe 0"], tpr_cl["classe 0"], _ = roc_curve( y_test == 0, y_proba[:, 0].ravel() ) fpr_cl["classe 1"], tpr_cl["classe 1"], _ = roc_curve( y_test, y_proba[:, 1].ravel() ) # y_test == 1 prob_pred = numpy.array([y_proba[i, 1 if c else 0] for i, c in enumerate(y_pred)]) fpr_cl["tout"], tpr_cl["tout"], _ = roc_curve((y_pred == y_test).ravel(), prob_pred) .. GENERATED FROM PYTHON SOURCE LINES 112-113 Et on les représente. .. GENERATED FROM PYTHON SOURCE LINES 113-126 .. code-block:: Python plt.figure() for key in fpr_cl: plt.plot(fpr_cl[key], tpr_cl[key], label=key) lw = 2 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("Proportion mal classée") plt.ylabel("Proportion bien classée") plt.title("ROC(s) avec predict_proba") plt.legend(loc="lower right") .. image-sg:: /auto_examples/ml/images/sphx_glr_plot_roc_002.png :alt: ROC(s) avec predict_proba :srcset: /auto_examples/ml/images/sphx_glr_plot_roc_002.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 127-141 predict_proba ou decision_function ---------------------------------- Le fait que la courbe :epkg:`ROC` pour la dernière question, les deux classes à la fois, suggère que les seuils optimaux seront différents pour les deux premières questions. La courbe :epkg:`ROC` ne change pas qu'on prenne la fonction `predict_proba `_ ou `decision_function `_ car ces deux scores sont liés par une fonction monotone. On recommence avec la seconde fonction. .. GENERATED FROM PYTHON SOURCE LINES 141-168 .. code-block:: Python y_pred = logreg.predict(X_test) y_proba = logreg.decision_function(X_test) y_proba = numpy.vstack([-y_proba, y_proba]).T fpr_cl["classe 0"], tpr_cl["classe 0"], _ = roc_curve( y_test == 0, y_proba[:, 0].ravel() ) fpr_cl["classe 1"], tpr_cl["classe 1"], _ = roc_curve( y_test, y_proba[:, 1].ravel() ) # y_test == 1 prob_pred = numpy.array([y_proba[i, 1 if c else 0] for i, c in enumerate(y_pred)]) fpr_cl["tout"], tpr_cl["tout"], _ = roc_curve((y_pred == y_test).ravel(), prob_pred) plt.figure() for key in fpr_cl: plt.plot(fpr_cl[key], tpr_cl[key], label=key) lw = 2 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("Proportion mal classée") plt.ylabel("Proportion bien classée") plt.title("ROC(s) avec decision_function") plt.legend(loc="lower right") .. image-sg:: /auto_examples/ml/images/sphx_glr_plot_roc_003.png :alt: ROC(s) avec decision_function :srcset: /auto_examples/ml/images/sphx_glr_plot_roc_003.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 169-181 Precision Rappel ---------------- En ce qui me concerne, je n'arrive jamais à retenir la définition de False Positive Rate (FPR) and True Positive Rate (TPR). Je lui préfère la précision et le rappel. Pour un seuil donné, le rappel est l'ensemble de ces documents dont le score est supérieur à un seuil *s*, la précision est l'ensemble des documents bien classé parmi ceux-ci. On utilise la fonction `precision_recall_curve `_. .. GENERATED FROM PYTHON SOURCE LINES 181-211 .. code-block:: Python y_pred = logreg.predict(X_test) y_proba = logreg.predict_proba(X_test) prec = dict() rapp = dict() prec["classe 0"], rapp["classe 0"], _ = precision_recall_curve( y_test == 0, y_proba[:, 0].ravel() ) prec["classe 1"], rapp["classe 1"], _ = precision_recall_curve( y_test, y_proba[:, 1].ravel() ) # y_test == 1 prob_pred = numpy.array([y_proba[i, 1 if c else 0] for i, c in enumerate(y_pred)]) prec["tout"], rapp["tout"], _ = precision_recall_curve( (y_pred == y_test).ravel(), prob_pred ) plt.figure() for key in fpr_cl: plt.plot(prec[key], rapp[key], label=key) plt.plot([0, 1], [0, 1], color="navy", lw=2, linestyle="--") plt.xlabel("Précision") plt.ylabel("Rappel") plt.title("Courbe Précision / Rappel") plt.legend(loc="lower right") .. image-sg:: /auto_examples/ml/images/sphx_glr_plot_roc_004.png :alt: Courbe Précision / Rappel :srcset: /auto_examples/ml/images/sphx_glr_plot_roc_004.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 212-222 Métrique F1 ----------- La courbe *Précision / Rappel* ne montre pas les scores même s'il intervient dans chaque point de la courbe. Pour le faire apparaître, on utilise un graphe où il est en abscisse. La métrique `F1 `_ propose une pondération entre les deux : :math:`F1 = 2 \frac{precision * rappel}{precision + rappel}`. .. GENERATED FROM PYTHON SOURCE LINES 222-251 .. code-block:: Python y_pred = logreg.predict(X_test) y_proba = logreg.predict_proba(X_test) prec, rapp, seuil = precision_recall_curve(y_test == 1, y_proba[:, 1].ravel()) f1 = [ f1_score(y_test[y_proba[:, 1] >= s].ravel(), y_pred[y_proba[:, 1] >= s]) for s in seuil.ravel() ] y_score = logreg.decision_function(X_test) precd, rappd, seuild = precision_recall_curve(y_test == 1, y_score.ravel()) f1d = [ f1_score(y_test[y_score >= s].ravel(), y_pred[y_score >= s]) for s in seuil.ravel() ] fig, ax = plt.subplots(1, 2, figsize=(12, 4)) ax[0].plot(seuil, prec[1:], label="Précision") ax[0].plot(seuil, rapp[1:], label="Rappel") ax[0].plot(seuil, f1, label="F1") ax[0].set_title("predict_proba") ax[0].legend() ax[1].plot(seuild, precd[1:], label="Précision") ax[1].plot(seuild, rappd[1:], label="Rappel") ax[1].plot(seuild, f1d, label="F1") ax[1].set_title("decision_function") ax[1].legend() .. image-sg:: /auto_examples/ml/images/sphx_glr_plot_roc_005.png :alt: predict_proba, decision_function :srcset: /auto_examples/ml/images/sphx_glr_plot_roc_005.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 252-267 Pourquoi ROC alors ? -------------------- On peut se demander pourquoi on utilise la courbe :epkg:`ROC` si d'autres graphiques sont plus compréhensibles. C'est parce que l'aire sous la courbe (`AUC `_) est relié à un résultat important : :math:`\mathbb{P}(S_F < S_T)` où :math:`S_F` représente la variable aléatoire *score pour une observation mal classée* et :math:`S_T` la variable aléatoire *score pour une observation bien classée* (voir `ROC `_). .. GENERATED FROM PYTHON SOURCE LINES 267-285 .. code-block:: Python y_pred = logreg.predict(X_test) y_proba = logreg.predict_proba(X_test) y_score = logreg.decision_function(X_test) fix, ax = plt.subplots(1, 2, figsize=(12, 4)) ax[0].hist(y_proba[y_test == 0, 1], color="r", label="proba -", alpha=0.5, bins=20) ax[0].hist(y_proba[y_test == 1, 1], color="b", label="proba +", alpha=0.5, bins=20) ax[0].set_title("predict_proba") ax[0].plot([0.8, 0.8], [0, 600], "--") ax[0].legend() ax[1].hist(y_score[y_test == 0], color="r", label="score -", alpha=0.5, bins=20) ax[1].hist(y_score[y_test == 1], color="b", label="score +", alpha=0.5, bins=20) ax[1].set_title("decision_function") ax[1].plot([1, 1], [0, 250], "--") ax[1].legend() .. image-sg:: /auto_examples/ml/images/sphx_glr_plot_roc_006.png :alt: predict_proba, decision_function :srcset: /auto_examples/ml/images/sphx_glr_plot_roc_006.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 286-297 La ligne en pointillés délimité la zone à partir de laquelle le modèle est sûr de sa décision. Elle est ajusté en fonction des besoins selon qu'on a besoin de plus de rappel (seuil bas) ou plus de précision (seuil haut). Le modèle est performant si les deux histogrammes sont bien séparés. Si on note *T(s)* l'aire bleue après la ligne en pointillé et *E(s)* l'aire rouge toujours après la ligne en pointillé. Ces deux quantités sont reliées à la distribution du score pour les bonnes et mauvaises prédictions. La courbe :epkg:`ROC` est constituée des point :math:`(1-T(s), 1-E(s))` lorsque le seuil *s* varie. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 6.840 seconds) .. _sphx_glr_download_auto_examples_ml_plot_roc.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_roc.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_roc.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_roc.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_