GetMLModelEvaluation

Finds a threshold for ML model, and performs an evaluation based on it.

python · Base

Details

IDGetMLModelEvaluation
Languagepython
From Version5.0.0
Docker Imagedemisto/ml:1.0.0.12042988
Tagsml

README

Finds a threshold for ML model, and performs an evaluation based on it

Script Data


Name Description
Script Type python3
Tags ml
Cortex XSOAR Version 5.0.0

Inputs


Argument Name Description
yTrue A list of labels of the test set
yPred A list of dictionaries contain probability predictions for all classes
targetPrecision minimum precision of all classes, ranges 0-1
targetRecall minimum recall of all classes, ranges 0-1
detailedOutput if set to ‘true’, the output will include a full explanation of the confidence threshold meaning

Outputs


Path Description Type
GetMLModelEvaluation.Threshold The found thresholds which meets the conditions of precision and recall String
GetMLModelEvaluation.ConfusionMatrixAtThreshold The model evaluation confusion matrix for mails above the threshold. Unknown
GetMLModelEvaluation.Metrics Metrics per each class (includes precision, true positive, coverage, etc.) Unknown
from GetMLModelEvaluation import find_threshold

y_true = []
y_pred = []
# add 7 correct instance of 1st class

y_true += ["class1"] * 7
y_pred += [{"class1": 0.8, "class2": 0.2}] * 7

# add 7 correct instance of 2nd class

y_true += ["class2"] * 7
y_pred += [{"class2": 0.8, "class1": 0.2}] * 7

# add 3 incorrect instance of 1st class at lower probability

y_true += ["class1"] * 3
y_pred += [{"class1": 0.3, "class2": 0.7}] * 3

# add 3 incorrect instance of 2nd class at lower probability

y_true += ["class2"] * 3
y_pred += [{"class2": 0.3, "class1": 0.7}] * 3

# 1 incorrect insance of 1st class at high probability
y_true += ["class1"] * 1
y_pred += [{"class1": 0.1, "class2": 0.9}] * 1

"""
class 1 precision per threshold:
-------------------------------------------
threshold == 0 (all predictions are 'class1)   ->  TP = 1 + 3 + 7  FP = 3 + 7 -> Precision = 0.63
threshold == 0.7 -> TP = 7  FP = 3  -> Precision = 0.7
threshold == 0.8 -> TP = 7  FP = 0 -> Precision = 1
"""

"""
class 2 precision per threshold:
-------------------------------------------
threshold == 0 (all predictions are 'class2)   ->  TP = 3 + 7  FP = 3 + 7 + 1 -> Precision = 0.63
threshold == 0.7 -> TP = 7  FP = 3 + 1  -> Precision = 0.636
threshold == 0.8 -> TP = 7  FP = 1 -> Precision = 0.875
threshold == 0.9 -> TP = 0  FP = 1 -> Precision = 0
"""


def test_threshold_found_0(mocker):
    global y_true, y_pred
    entry = find_threshold(y_pred_all_classes=y_pred, y_true=y_true, customer_target_precision=0, target_recall=0)
    assert abs(entry["Contents"]["threshold"] - 0.7) < 10**-2


def test_threshold_found_1(mocker):
    global y_true, y_pred
    entry = find_threshold(y_pred_all_classes=y_pred, y_true=y_true, customer_target_precision=0.63, target_recall=0)
    assert abs(entry["Contents"]["threshold"] - 0.7) < 10**-2


def test_threshold_found_2(mocker):
    global y_true, y_pred
    entry = find_threshold(y_pred_all_classes=y_pred, y_true=y_true, customer_target_precision=0.7, target_recall=0)
    assert abs(entry["Contents"]["threshold"] - 0.8) < 10**-2


def test_threshold_found_3(mocker):
    global y_true, y_pred
    entry = find_threshold(y_pred_all_classes=y_pred, y_true=y_true, customer_target_precision=0.875, target_recall=0)
    assert abs(entry["Contents"]["threshold"] - 0.8) < 10**-2


def test_no_existing_threshold(mocker):
    entry = find_threshold(y_pred_all_classes=y_pred, y_true=y_true, customer_target_precision=0.9, target_recall=0)
    assert abs(entry["Contents"]["threshold"] - 0.8) < 10**-2


def test_predictions_are_correct_and_all_equals_one_prob(mocker):
    y_true = ["class1"] * 7 + ["class2"] * 7
    y_pred = [{"class1": 0.95}] * 7 + [{"class2": 0.95}] * 7
    entry = find_threshold(y_pred_all_classes=y_pred, y_true=y_true, customer_target_precision=0.6, target_recall=0)
    assert abs(entry["Contents"]["threshold"] - 0.95) < 10**-2


def test_predictions_are_correct_and_almost_all_equals_one_prob(mocker):
    y_true = ["class1"] * 7 + ["class2"] * 7
    y_pred = [{"class1": 1}] * 6 + [{"class1": 0.95}] + [{"class2": 1}] * 7
    entry = find_threshold(y_pred_all_classes=y_pred, y_true=y_true, customer_target_precision=0.6, target_recall=0)
    assert abs(entry["Contents"]["threshold"] - 0.95) < 10**-2


def test_plabook_test_simulation(mocker):
    y_pred = [{"spam": 0.9987042546272278}, {"ham": 0.9987037777900696}]
    y_true = ["spam", "ham"]
    entry = find_threshold(y_pred_all_classes=y_pred, y_true=y_true, customer_target_precision=0.7, target_recall=0)
    assert abs(entry["Contents"]["threshold"] - 0.9987037777900696) < 10**-2


def test_all_wrong_predictions(mocker):
    y_true = ["class1"] * 7 + ["class2"] * 7
    y_pred = [{"class2": 0.5}] * 7 + [{"class1": 0.5}] * 7
    entry = find_threshold(y_pred_all_classes=y_pred, y_true=y_true, customer_target_precision=0.6, target_recall=0)
    assert entry["Contents"]["threshold"] >= 0.5


def test_all_wrong_predictions_2(mocker):
    y_true = ["class1"] * 7 + ["class2"] * 7
    y_pred = [{"class2": 0.5}] * 7 + [{"class1": 0.5}] * 7
    entry = find_threshold(y_pred_all_classes=y_pred, y_true=y_true, customer_target_precision=0, target_recall=0)
    assert entry["Contents"]["threshold"] >= 0.5