Professional Documents
Culture Documents
Model Evaluation - II
Model Evaluation - II
Cookbook".
The diagonal elements of the confusion matrix show the number of correct predictions and
the off-diagonal elements show the number of incorrect predictions.
Accuracy:
Accuracy is a common performance metric. It is simply the proportion of observations
predicted correctly.
T P+T N
A c c u r a c y=
T P+T N + F P+ F N
where
• T P : number of true positives. Observations that are part of the positive class (has
the disease, purchased the product, etc.) and that we predicted correctly.
• T N : number of true negatives. Observations that are part of the negative class (does
not have the disease, did not purchase the product, etc.) and that we predicted
correctly.
We can measure accuracy in 5-fold (the default number of folds) cross-validation by setting
scoring="accuracy".
# Load libraries
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
The appeal of accuracy is that it has an intuitive and plain English explanation: proportion
of observations predicted correctly.
However, in the real world, often our data has imbalanced classes (e.g., the 99.9% of
observations are of class 1 and only 0.1% are class 2). When in the presence of imbalanced
classes, accuracy suffers from a paradox where a model is highly accurate but lacks
predictive power. For example, imagine we are trying to predict the presence of a very rare
cancer that occurs in 0.1% of the population. After training our model, we find the accuracy
is at 95%. However, 99.9% of people do not have the cancer: if we simply created a model
that "predicted" that nobody had that form of cancer, our naive model would be 4.9% more
accurate, but clearly is not able to predict anything. For this reason, we are often
motivated to use other metrics like precision, recall, and the F 1 score.
Precision:
Precision is the proportion of every observation predicted to be positive that is actually
positive. We can think about it as a measurement noise in our predictions--that is, when we
predict something is positive, how likely we are to be right.
TP
P r e c i s io n=
T P+ F P
Models with high precision are pessimistic in that they only predict an observation is of the
positive class when they are very certain about it.
# Cross-validate model using precision
cross_val_score(logit, X, y, scoring="precision")
Recall/Sensitivity/Power:
Recall is the proportion of every positive observation that is truly positive. Recall measures
the model's ability to identify an observation of the positive class. Models with high recall
are optimistic in that they have a low bar for predicting that an observation is in the
positive class.
TP
R e c a ll=
T P+ F N
# Cross-validate model using recall
cross_val_score(logit, X, y, scoring="recall")
F 1 score:
Note that precision and recall are a bit less intuitive. Almost always we want some kind of
balance between precision and recall--that is, a trade-off between the optimism and
pessimism of our model, and this role is filled by the F 1 score. The F 1 score is the harmonic
mean (a kind of average used for ratios).
P r e c i s i o n × R e c a ll
F 1=2×
P r e c i s i o n+ R e c al l
It is a measure of correctness achieved in positive prediction--that is, of observations
labeled as positive, how many are actually positive.
# Cross-validate model using f1
cross_val_score(logit, X, y, scoring="f1")
As an evaluation metric, accuracy has some valuable properties, especially its simple
intuition. However, better metrics often involve using some balance of precision and
recall--that is, a trade-off between the optimism and pessimism of our model. F 1 represents
a balance between the recall and precision, where the relative contributions of both are
equal.
Alternatively to using cross_val_score, if we already have the true y values and the
predicted y values, we can calculate metrics like accuracy and recall directly.
# Load library
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score,
recall_score, f1_score
# Calculate accuracy
accuracy_score(y_test, y_hat)
0.947
# Calculate precision
precision_score(y_test, y_hat)
0.9531568228105907
# Calculate recall
recall_score(y_test, y_hat)
0.9397590361445783
# Calculate f1_score
f1_score(y_test, y_hat)
0.9464105156723963
Specificity:
Specificity is the proportion of every negative observation that is truly negative. Specificity
measures the model's ability to identify an observation of the negative class.
TN
S p e c i f i c i t y=
T N +F P
We want both recall/sensitivity and specificity to be high.
# Create classifier
logit = LogisticRegression()
# Train model
logit.fit(X_train, y_train)
array([[0.86891533, 0.13108467]])
array([0, 1])
In this example, the first observation has an ~87% chance of being in the negative class (0)
and a 13% chance of being in the positive class (1). By default, scikit-learn predicts an
observation is part of the positive class if the probability is greater than 0.5 (called the
threshold).
However, instead of a middle ground, we will often want to explicitly bias our model to use
a different threshold for substantive reasons. For example, if a false positive is very costly
to our company, we might prefer a model that has a high probability threshold. We fail to
predict some positives, but when an observation is predicted to be positive, we can be very
confident that the prediction is correct. This trade-off is represented in the true positive
rate (TPR) and the false positive rate (FPR). TPR is the number of observations correctly
predicted true divided by all true positive observations.
TP
T P R= =S e n s i t i v i t y
T P+ F N
FPR is the number of incorrectly predicted positives divided by all true negative
observations.
FP
F P R= =1 − S p e c i f i c i t y
F P+T N
The ROC curve represents the respective TPR and FPR for every probability threshold. For
example, in our solution a threshold of roughly 0.5 has a TPR of 0.81 and an FPR of 0.15.
print("Threshold:", threshold[116])
print("True Positive Rate:", true_positive_rate[116])
print("False Positive Rate:", false_positive_rate[116])
Threshold: 0.5331715230155316
True Positive Rate: 0.810204081632653
False Positive Rate: 0.14901960784313725
However, if we increase the threshold to ~80% (i.e., increase how certain the model has to
be before it predicts an observation as positive) the TPR drops significantly but so does the
FPR:
print("Threshold:", threshold[45])
print("True Positive Rate:", true_positive_rate[45])
print("False Positive Rate:", false_positive_rate[45])
Threshold: 0.8189133876659292
True Positive Rate: 0.5448979591836735
False Positive Rate: 0.047058823529411764
This is because our higher requirement for being predicted to be in the positive class has
made the model not identify a number of positive observations (the lower TPR), but also
reduce the noise from negative observations being predicted as positive (the lower FPR).
In addition to being able to visualize the trade-off between TPR and FPR, the ROC curve can
also be used as a general metric for a model. The better a model is, the higher the curve and
thus the greater the area under the curve. For this reason, it is common to calculate the
area under the ROC curve (AUC) to judge the overall equality of a model at all possible
thresholds. The closer the AUC is to 1, the better the model.
In scikit-learn we can calculate the AUC using roc_auc_score.
# Calculate area under curve
roc_auc_score(y_test, y_probabilities)
0.9073389355742297
Evaluating multiclass classifier predictions
# Load libraries
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
When we have balanced classes (e.g., a roughly equal number of observations in each class
of the target/ y vector), accuracy is--just like in the binary class setting--a simple and
interpretable choice for an evaluation metric. Accuracy is the number of correct predictions
divided by the number of observations and works just as well in the multiclass as binary
setting. However, when we have imbalanced classes (a common scenario), we should be
inclined to use other evaluation metrics.
Many of scikit-learn's built-in metrics are for evaluating binary classifiers. However,
many of these metrics can be extended for use when we have more than two classes. While
precision, recall, and F 1 scores were originally designed for binary classifiers, we can apply
them to multiclass settings by treating our data as a set of binary classes. Doing so enables
us to apply the metrics to each class as if it were the only class in the data, and then
aggregate the evaluation scores for all the classes by averaging them.
# Cross-validate model using macro averaged F1 score
cross_val_score(logit, X, y, scoring="f1_macro")
In this code, _macro refers to the method used to average the evaluation scores from the
classes:
• macro: Calculate mean of metric scores for each class, weighting each class equally
• weighted: Calculate mean of metric scores for each class, weighting each class
proportional to its size in the data
# Load data
iris = datasets.load_iris()
# Create heatmap
sns.heatmap(dataframe, annot=True, cbar=None, cmap="Blues")
plt.title("Confusion Matrix"), plt.tight_layout()
plt.ylabel("True Class"), plt.xlabel("Predicted Class")
plt.show()
/usr/local/lib/python3.8/dist-packages/sklearn/linear_model/
_logistic.py:814: ConvergenceWarning: lbfgs failed to converge
(status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
https://scikit-learn.org/stable/modules/linear_model.html#logistic-
regression
n_iter_i = _check_optimize_result(
# Load data
iris = datasets.load_iris()
/usr/local/lib/python3.8/dist-packages/sklearn/linear_model/
_logistic.py:814: ConvergenceWarning: lbfgs failed to converge
(status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
https://scikit-learn.org/stable/modules/linear_model.html#logistic-
regression
n_iter_i = _check_optimize_result(