|
92 | 92 | """
|
93 | 93 | # %%
|
94 | 94 | # In binary classification settings
|
95 |
| -# -------------------------------------------------------- |
| 95 | +# --------------------------------- |
96 | 96 | #
|
97 |
| -# Create simple data |
98 |
| -# .................. |
| 97 | +# Dataset and model |
| 98 | +# ................. |
99 | 99 | #
|
100 |
| -# Try to differentiate the two first classes of the iris data |
101 |
| -from sklearn import svm, datasets |
102 |
| -from sklearn.model_selection import train_test_split |
| 100 | +# We will use a Linear SVC classifier to differentiate two types of irises. |
103 | 101 | import numpy as np
|
| 102 | +from sklearn.datasets import load_iris |
| 103 | +from sklearn.model_selection import train_test_split |
104 | 104 |
|
105 |
| -iris = datasets.load_iris() |
106 |
| -X = iris.data |
107 |
| -y = iris.target |
| 105 | +X, y = load_iris(return_X_y=True) |
108 | 106 |
|
109 | 107 | # Add noisy features
|
110 | 108 | random_state = np.random.RandomState(0)
|
111 | 109 | n_samples, n_features = X.shape
|
112 |
| -X = np.c_[X, random_state.randn(n_samples, 200 * n_features)] |
| 110 | +X = np.concatenate([X, random_state.randn(n_samples, 200 * n_features)], axis=1) |
113 | 111 |
|
114 | 112 | # Limit to the two first classes, and split into training and test
|
115 |
| -X_train, X_test, y_train, y_test = train_test_split(X[y < 2], y[y < 2], |
116 |
| - test_size=.5, |
117 |
| - random_state=random_state) |
| 113 | +X_train, X_test, y_train, y_test = train_test_split( |
| 114 | + X[y < 2], y[y < 2], test_size=0.5, random_state=random_state |
| 115 | +) |
118 | 116 |
|
119 |
| -# Create a simple classifier |
120 |
| -classifier = svm.LinearSVC(random_state=random_state) |
| 117 | +# %% |
| 118 | +# Linear SVC will expect each feature to have a similar range of values. Thus, |
| 119 | +# we will first scale the data using a |
| 120 | +# :class:`~sklearn.preprocessing.StandardScaler`. |
| 121 | +from sklearn.pipeline import make_pipeline |
| 122 | +from sklearn.preprocessing import StandardScaler |
| 123 | +from sklearn.svm import LinearSVC |
| 124 | + |
| 125 | +classifier = make_pipeline(StandardScaler(), LinearSVC(random_state=random_state)) |
121 | 126 | classifier.fit(X_train, y_train)
|
122 |
| -y_score = classifier.decision_function(X_test) |
123 | 127 |
|
124 | 128 | # %%
|
125 |
| -# Compute the average precision score |
126 |
| -# ................................... |
127 |
| -from sklearn.metrics import average_precision_score |
128 |
| -average_precision = average_precision_score(y_test, y_score) |
| 129 | +# Plot the Precision-Recall curve |
| 130 | +# ............................... |
| 131 | +# |
| 132 | +# To plot the precision-recall curve, you should use |
| 133 | +# :class:`~sklearn.metrics.PrecisionRecallDisplay`. Indeed, there is two |
| 134 | +# methods available depending if you already computed the predictions of the |
| 135 | +# classifier or not. |
| 136 | +# |
| 137 | +# Let's first plot the precision-recall curve without the classifier |
| 138 | +# predictions. We use |
| 139 | +# :func:`~sklearn.metrics.PrecisionRecallDisplay.from_estimator` that |
| 140 | +# computes the predictions for us before plotting the curve. |
| 141 | +from sklearn.metrics import PrecisionRecallDisplay |
129 | 142 |
|
130 |
| -print('Average precision-recall score: {0:0.2f}'.format( |
131 |
| - average_precision)) |
| 143 | +display = PrecisionRecallDisplay.from_estimator( |
| 144 | + classifier, X_test, y_test, name="LinearSVC" |
| 145 | +) |
| 146 | +_ = display.ax_.set_title("2-class Precision-Recall curve") |
132 | 147 |
|
133 | 148 | # %%
|
134 |
| -# Plot the Precision-Recall curve |
135 |
| -# ................................ |
136 |
| -from sklearn.metrics import precision_recall_curve |
137 |
| -from sklearn.metrics import plot_precision_recall_curve |
138 |
| -import matplotlib.pyplot as plt |
| 149 | +# If we already got the estimated probabilities or scores for |
| 150 | +# our model, then we can use |
| 151 | +# :func:`~sklearn.metrics.PrecisionRecallDisplay.from_predictions`. |
| 152 | +y_score = classifier.decision_function(X_test) |
139 | 153 |
|
140 |
| -disp = plot_precision_recall_curve(classifier, X_test, y_test) |
141 |
| -disp.ax_.set_title('2-class Precision-Recall curve: ' |
142 |
| - 'AP={0:0.2f}'.format(average_precision)) |
| 154 | +display = PrecisionRecallDisplay.from_predictions(y_test, y_score, name="LinearSVC") |
| 155 | +_ = display.ax_.set_title("2-class Precision-Recall curve") |
143 | 156 |
|
144 | 157 | # %%
|
145 | 158 | # In multi-label settings
|
146 |
| -# ------------------------ |
| 159 | +# ----------------------- |
| 160 | +# |
| 161 | +# The precision-recall curve does not support the multilabel setting. However, |
| 162 | +# one can decide how to handle this case. We show such an example below. |
147 | 163 | #
|
148 | 164 | # Create multi-label data, fit, and predict
|
149 |
| -# ........................................... |
| 165 | +# ......................................... |
150 | 166 | #
|
151 | 167 | # We create a multi-label dataset, to illustrate the precision-recall in
|
152 |
| -# multi-label settings |
| 168 | +# multi-label settings. |
153 | 169 |
|
154 | 170 | from sklearn.preprocessing import label_binarize
|
155 | 171 |
|
|
158 | 174 | n_classes = Y.shape[1]
|
159 | 175 |
|
160 | 176 | # Split into training and test
|
161 |
| -X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=.5, |
162 |
| - random_state=random_state) |
| 177 | +X_train, X_test, Y_train, Y_test = train_test_split( |
| 178 | + X, Y, test_size=0.5, random_state=random_state |
| 179 | +) |
163 | 180 |
|
164 |
| -# We use OneVsRestClassifier for multi-label prediction |
| 181 | +# %% |
| 182 | +# We use :class:`~sklearn.multiclass.OneVsRestClassifier` for multi-label |
| 183 | +# prediction. |
165 | 184 | from sklearn.multiclass import OneVsRestClassifier
|
166 | 185 |
|
167 |
| -# Run classifier |
168 |
| -classifier = OneVsRestClassifier(svm.LinearSVC(random_state=random_state)) |
| 186 | +classifier = OneVsRestClassifier( |
| 187 | + make_pipeline(StandardScaler(), LinearSVC(random_state=random_state)) |
| 188 | +) |
169 | 189 | classifier.fit(X_train, Y_train)
|
170 | 190 | y_score = classifier.decision_function(X_test)
|
171 | 191 |
|
172 | 192 |
|
173 | 193 | # %%
|
174 | 194 | # The average precision score in multi-label settings
|
175 |
| -# .................................................... |
| 195 | +# ................................................... |
| 196 | +from sklearn.metrics import precision_recall_curve |
176 | 197 | from sklearn.metrics import average_precision_score
|
177 | 198 |
|
178 | 199 | # For each class
|
179 | 200 | precision = dict()
|
180 | 201 | recall = dict()
|
181 | 202 | average_precision = dict()
|
182 | 203 | for i in range(n_classes):
|
183 |
| - precision[i], recall[i], _ = precision_recall_curve(Y_test[:, i], |
184 |
| - y_score[:, i]) |
| 204 | + precision[i], recall[i], _ = precision_recall_curve(Y_test[:, i], y_score[:, i]) |
185 | 205 | average_precision[i] = average_precision_score(Y_test[:, i], y_score[:, i])
|
186 | 206 |
|
187 | 207 | # A "micro-average": quantifying score on all classes jointly
|
188 |
| -precision["micro"], recall["micro"], _ = precision_recall_curve(Y_test.ravel(), |
189 |
| - y_score.ravel()) |
190 |
| -average_precision["micro"] = average_precision_score(Y_test, y_score, |
191 |
| - average="micro") |
192 |
| -print('Average precision score, micro-averaged over all classes: {0:0.2f}' |
193 |
| - .format(average_precision["micro"])) |
| 208 | +precision["micro"], recall["micro"], _ = precision_recall_curve( |
| 209 | + Y_test.ravel(), y_score.ravel() |
| 210 | +) |
| 211 | +average_precision["micro"] = average_precision_score(Y_test, y_score, average="micro") |
194 | 212 |
|
195 | 213 | # %%
|
196 | 214 | # Plot the micro-averaged Precision-Recall curve
|
197 |
| -# ............................................... |
198 |
| -# |
199 |
| - |
200 |
| -plt.figure() |
201 |
| -plt.step(recall['micro'], precision['micro'], where='post') |
202 |
| - |
203 |
| -plt.xlabel('Recall') |
204 |
| -plt.ylabel('Precision') |
205 |
| -plt.ylim([0.0, 1.05]) |
206 |
| -plt.xlim([0.0, 1.0]) |
207 |
| -plt.title( |
208 |
| - 'Average precision score, micro-averaged over all classes: AP={0:0.2f}' |
209 |
| - .format(average_precision["micro"])) |
| 215 | +# .............................................. |
| 216 | +display = PrecisionRecallDisplay( |
| 217 | + recall=recall["micro"], |
| 218 | + precision=precision["micro"], |
| 219 | + average_precision=average_precision["micro"], |
| 220 | +) |
| 221 | +display.plot() |
| 222 | +_ = display.ax_.set_title("Micro-averaged over all classes") |
210 | 223 |
|
211 | 224 | # %%
|
212 | 225 | # Plot Precision-Recall curve for each class and iso-f1 curves
|
213 |
| -# ............................................................. |
214 |
| -# |
| 226 | +# ............................................................ |
| 227 | +import matplotlib.pyplot as plt |
215 | 228 | from itertools import cycle
|
| 229 | + |
216 | 230 | # setup plot details
|
217 |
| -colors = cycle(['navy', 'turquoise', 'darkorange', 'cornflowerblue', 'teal']) |
| 231 | +colors = cycle(["navy", "turquoise", "darkorange", "cornflowerblue", "teal"]) |
| 232 | + |
| 233 | +_, ax = plt.subplots(figsize=(7, 8)) |
218 | 234 |
|
219 |
| -plt.figure(figsize=(7, 8)) |
220 | 235 | f_scores = np.linspace(0.2, 0.8, num=4)
|
221 |
| -lines = [] |
222 |
| -labels = [] |
| 236 | +lines, labels = [], [] |
223 | 237 | for f_score in f_scores:
|
224 | 238 | x = np.linspace(0.01, 1)
|
225 | 239 | y = f_score * x / (2 * x - f_score)
|
226 |
| - l, = plt.plot(x[y >= 0], y[y >= 0], color='gray', alpha=0.2) |
227 |
| - plt.annotate('f1={0:0.1f}'.format(f_score), xy=(0.9, y[45] + 0.02)) |
| 240 | + (l,) = plt.plot(x[y >= 0], y[y >= 0], color="gray", alpha=0.2) |
| 241 | + plt.annotate("f1={0:0.1f}".format(f_score), xy=(0.9, y[45] + 0.02)) |
228 | 242 |
|
229 |
| -lines.append(l) |
230 |
| -labels.append('iso-f1 curves') |
231 |
| -l, = plt.plot(recall["micro"], precision["micro"], color='gold', lw=2) |
232 |
| -lines.append(l) |
233 |
| -labels.append('micro-average Precision-recall (area = {0:0.2f})' |
234 |
| - ''.format(average_precision["micro"])) |
| 243 | +display = PrecisionRecallDisplay( |
| 244 | + recall=recall["micro"], |
| 245 | + precision=precision["micro"], |
| 246 | + average_precision=average_precision["micro"], |
| 247 | +) |
| 248 | +display.plot(ax=ax, name="Micro-average precision-recall", color="gold") |
235 | 249 |
|
236 | 250 | for i, color in zip(range(n_classes), colors):
|
237 |
| - l, = plt.plot(recall[i], precision[i], color=color, lw=2) |
238 |
| - lines.append(l) |
239 |
| - labels.append('Precision-recall for class {0} (area = {1:0.2f})' |
240 |
| - ''.format(i, average_precision[i])) |
241 |
| - |
242 |
| -fig = plt.gcf() |
243 |
| -fig.subplots_adjust(bottom=0.25) |
244 |
| -plt.xlim([0.0, 1.0]) |
245 |
| -plt.ylim([0.0, 1.05]) |
246 |
| -plt.xlabel('Recall') |
247 |
| -plt.ylabel('Precision') |
248 |
| -plt.title('Extension of Precision-Recall curve to multi-class') |
249 |
| -plt.legend(lines, labels, loc=(0, -.38), prop=dict(size=14)) |
250 |
| - |
| 251 | + display = PrecisionRecallDisplay( |
| 252 | + recall=recall[i], |
| 253 | + precision=precision[i], |
| 254 | + average_precision=average_precision[i], |
| 255 | + ) |
| 256 | + display.plot(ax=ax, name=f"Precision-recall for class {i}", color=color) |
| 257 | + |
| 258 | +# add the legend for the iso-f1 curves |
| 259 | +handles, labels = display.ax_.get_legend_handles_labels() |
| 260 | +handles.extend([l]) |
| 261 | +labels.extend(["iso-f1 curves"]) |
| 262 | +# set the legend and the axes |
| 263 | +ax.set_xlim([0.0, 1.0]) |
| 264 | +ax.set_ylim([0.0, 1.05]) |
| 265 | +ax.legend(handles=handles, labels=labels, loc="best") |
| 266 | +ax.set_title("Extension of Precision-Recall curve to multi-class") |
251 | 267 |
|
252 | 268 | plt.show()
|
0 commit comments