aboutsummaryrefslogtreecommitdiffhomepage
path: root/sec_certs/model/evaluation.py
diff options
context:
space:
mode:
Diffstat (limited to 'sec_certs/model/evaluation.py')
-rw-r--r--sec_certs/model/evaluation.py102
1 files changed, 82 insertions, 20 deletions
diff --git a/sec_certs/model/evaluation.py b/sec_certs/model/evaluation.py
index ac070626..353fa6e6 100644
--- a/sec_certs/model/evaluation.py
+++ b/sec_certs/model/evaluation.py
@@ -1,5 +1,6 @@
import json
from pathlib import Path
+import logging
from typing import Dict, List, Set, Optional, Union
import numpy as np
@@ -8,6 +9,10 @@ from sec_certs.dataset.common_criteria import CCDataset
from sec_certs.certificate.common_criteria import CommonCriteriaCert
from sec_certs.dataset.cve import CVEDataset, CVE
from sec_certs.serialization import CustomJSONEncoder
+import sec_certs.helpers as helpers
+import tqdm
+
+logger = logging.getLogger(__name__)
def binarize_labels(cve_dset: CVEDataset, instances: List[List[CVE]]) -> np.array:
@@ -27,15 +32,19 @@ def get_validation_dgsts(filepath: Union[str, Path]) -> Set[str]:
return set(data.keys())
-def get_y_true(certs: List[CommonCriteriaCert]) -> List[List[CVE]]:
- return [set(cert.heuristics.related_cves) if cert.heuristics.related_cves else [] for cert in certs]
+def get_y_true(certs: List[CommonCriteriaCert]) -> np.array:
+ return np.array([np.array([y.cve_id for y in cert.heuristics.related_cves]) if cert.heuristics.related_cves else np.array(['None']) for cert in certs], dtype='object')
-def compute_precision(y: List[List[CVE]], y_pred: List[List[CVE]], **kwargs):
+def compute_precision(y: np.array, y_pred: np.array, **kwargs):
prec = []
- for pred, true in zip(y_pred, y):
+ for true, pred in zip(y, y_pred):
set_pred = set(pred)
+ if 'None' in set_pred:
+ set_pred.remove('None')
set_true = set(true)
+ if 'None' in set_true:
+ set_true.remove('None')
if set_pred and not set_true:
prec.append(0)
@@ -46,25 +55,78 @@ def compute_precision(y: List[List[CVE]], y_pred: List[List[CVE]], **kwargs):
return np.mean(prec)
-def prepare_classification_report(cert_names: List[str], y_pred: List[List[CVE]], y_true: List[List[CVE]],
- keywords: Set[str],
- distances: Optional[List[List[float]]],
- out_filepath: Optional[Union[str, Path]] = None):
+def compute_promising_ratio(y: np.array, y_pred: np.array):
+ """
+ Computes number of matched vulnerabilities that have lower distance from a certificate than the
+ first already discovered vulnerability (any from y). If no new vulnerability with such property was identified,
+ 0 is assigned instead. Assumes that vulnerabilities are ordered by their similarity to given certificate.
+ """
+ if len(y_pred) > 200:
+ logger.warning('Promising matches metric should be computed only on certificates with ground-truth-verified vulnerability.')
+
+ n_promising = []
+ for instance, ground_truth_vulns in zip(y_pred, y):
+ known_before = np.array(list(map(lambda x: x in set(ground_truth_vulns), instance)))
+ true_indices = np.where(known_before)
+ if true_indices[0].size > 0 and true_indices[0][0] != 0:
+ n_promising.append(true_indices[0][0])
+ else:
+ n_promising.append(0)
+
+ return np.mean(n_promising)
+
+
+def prepare_classification_report(cert_names, y_pred, y_true, distances, cve_dataset, keywords, classifier, out_path):
+ def get_cve_representation(cve_dataset, cve_id, keywords, classifier):
+ if cve_id == 'None':
+ return None,
+ else:
+ return {
+ 'cve_id': cve_id,
+ 'description': cve_dataset[cve_id].description,
+ 'tokenized': helpers.tokenize(cve_dataset[cve_id].description, keywords),
+ 'tfidf': classifier.prepare_df_from_description(helpers.tokenize(cve_dataset[cve_id].description, keywords))['TF-IDF'].to_dict(),
+ }
+
correctly_classified = []
badly_classified = []
results = {'correctly_classified': correctly_classified, 'badly_classified': badly_classified}
- for index, cert in enumerate(cert_names):
- outcome = {'certificate name': cert, 'prediction': [x.to_brief_dict(keywords) for x in y_pred[index]],
- 'ground_truth': [x.to_brief_dict(keywords) for x in y_true[index]]}
- if distances:
- outcome['distances'] = distances[index]
-
- if set(y_true[index]).issubset(set(y_pred[index])):
- correctly_classified.append(outcome)
+ for crt, prediction, ground_truth, dis in tqdm.tqdm(zip(cert_names, y_pred, y_true, distances), desc='Preparing classification report', total=len(cert_names)):
+ record = {'certificate_name': crt,
+ 'tokenized': helpers.tokenize(crt, keywords),
+ 'tfidf': classifier.prepare_df_from_description(crt)['TF-IDF'].to_dict(),
+ 'distances': dis,
+ 'predicted_cves': [get_cve_representation(cve_dataset, cve_id, keywords, classifier) for cve_id in prediction],
+ 'true_cves': [get_cve_representation(cve_dataset, cve_id, keywords, classifier) for cve_id in ground_truth]}
+ if set(ground_truth).issubset(set(prediction)):
+ correctly_classified.append(record)
else:
- badly_classified.append(outcome)
+ badly_classified.append(record)
+
+ with Path(out_path).open('w') as handle:
+ json.dump(results, handle, indent=4)
+
- if out_filepath:
- with Path(out_filepath).open('w') as handle:
- json.dump(results, handle, indent=4, cls=CustomJSONEncoder)
+# def prepare_classification_report(cert_names: List[str], y_pred: List[List[CVE]], y_true: List[List[CVE]],
+# keywords: Set[str],
+# distances: Optional[List[List[float]]],
+# out_filepath: Optional[Union[str, Path]] = None):
+# correctly_classified = []
+# badly_classified = []
+# results = {'correctly_classified': correctly_classified, 'badly_classified': badly_classified}
+#
+# for index, cert in enumerate(cert_names):
+# outcome = {'certificate name': cert, 'prediction': [x.to_brief_dict(keywords) for x in y_pred[index]],
+# 'ground_truth': [x.to_brief_dict(keywords) for x in y_true[index]]}
+# if distances:
+# outcome['distances'] = distances[index]
+#
+# if set(y_true[index]).issubset(set(y_pred[index])):
+# correctly_classified.append(outcome)
+# else:
+# badly_classified.append(outcome)
+#
+# if out_filepath:
+# with Path(out_filepath).open('w') as handle:
+# json.dump(results, handle, indent=4, cls=CustomJSONEncoder)