-
Notifications
You must be signed in to change notification settings - Fork 5
Minor modifications #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mam10eks
wants to merge
4
commits into
sisap-challenges:main
Choose a base branch
from
mam10eks:minor-modifications
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,49 @@ | ||
| #!/usr/bin/env python3 | ||
| import argparse | ||
| import h5py | ||
| import numpy as np | ||
| import csv | ||
| import glob | ||
| import json | ||
| from pathlib import Path | ||
| from tqdm import tqdm | ||
| from datasets import DATASETS, get_fn, prepare, get_h5_item | ||
|
|
||
|
|
||
| def get_all_results(dirname): | ||
| """Yield (path, attrs, knns) for every valid result .h5 file under dirname.""" | ||
| """Return all paths with result .h5 file under dirname.""" | ||
| seen = set() | ||
| mask = dirname + "/**/*.h5" | ||
| print(f"Searching for results matching: {mask}") | ||
| ret = [] | ||
| for fn in glob.iglob(mask, recursive=True): | ||
| if fn in seen: | ||
| continue | ||
| seen.add(fn) | ||
| with h5py.File(fn, "r") as f: | ||
| if "knns" not in f or "dataset" not in f.attrs or "task" not in f.attrs: | ||
| print(f"Ignoring {fn}") | ||
| continue | ||
| print(fn) | ||
| yield fn, dict(f.attrs), np.array(f["knns"]) | ||
| try: | ||
| with h5py.File(fn, "r") as f: | ||
| if "knns" not in f: | ||
| print(f"Ignoring {fn}") | ||
| continue | ||
|
|
||
| ret += [fn] | ||
| except: | ||
| print(f"Could not load {fn}") | ||
|
|
||
| return ret | ||
|
|
||
|
|
||
| def load_results(fn): | ||
| with h5py.File(fn, "r") as f: | ||
| attrs = dict(f.attrs) | ||
| for k, v in attrs.items(): | ||
| if isinstance(v, bytes): | ||
| attrs[k] = v.decode("UTF-8") | ||
| if isinstance(v, np.int64): | ||
| attrs[k] = int(v) | ||
| if isinstance(v, np.float32): | ||
| attrs[k] = float(v) | ||
| return attrs, np.array(f["knns"]) | ||
|
|
||
|
|
||
| def get_recall(I, gt, k): | ||
|
|
@@ -36,19 +59,27 @@ def get_recall(I, gt, k): | |
|
|
||
| Always compares the k nearest neighbors EXCLUDING self-loops. | ||
| """ | ||
| if I.shape[0] != gt.shape[0]: | ||
| return (-1, f"Query count mismatch {I.shape[0]} vs. {gt.shape[0]}") | ||
|
|
||
| assert I.shape[0] == gt.shape[0], "query count mismatch between results and ground truth" | ||
| assert gt.shape[1] >= k + 1, f"Ground truth needs at least {k+1} columns (self + {k} neighbors), got {gt.shape[1]}" | ||
|
|
||
| msg = None | ||
| if I.shape[1] == k + 1: | ||
| # Results include self-loops: use columns [1:k+1] (skip first column) | ||
| I_to_compare = I[:, 1:k+1] | ||
| print(f" Results shape {I.shape}: assumed self-loops, comparing columns [1:{k+1}]") | ||
| msg = f"OK, Results shape {I.shape}: assumed self-loops, comparing columns [1:{k+1}]" | ||
| print(msg) | ||
| elif I.shape[1] == k: | ||
| # Results exclude self-loops: use columns [:k] | ||
| I_to_compare = I[:, :k] | ||
| print(f" Results shape {I.shape}: assumed no self-loops, comparing columns [0:{k}]") | ||
| msg = f"Ok, Results shape {I.shape}: assumed no self-loops, comparing columns [0:{k}]" | ||
| print(msg) | ||
| else: | ||
| raise ValueError(f"Results shape {I.shape} is neither {I.shape[0]}x{k} nor {I.shape[0]}x{k+1}") | ||
| msg = f"Results shape {I.shape} is neither {I.shape[0]}x{k} nor {I.shape[0]}x{k+1}" | ||
| print(msg) | ||
| return (-1, msg) | ||
|
|
||
| # Ground truth: always skip first column (self-loop) and use next k columns | ||
| gt_to_compare = gt[:, 1:k+1] # Skip self-loop, take next k neighbors | ||
|
|
@@ -57,7 +88,39 @@ def get_recall(I, gt, k): | |
| len(np.intersect1d(I_to_compare[i], gt_to_compare[i])) | ||
| for i in range(len(I)) | ||
| ]) | ||
| return hits.sum() / (len(I) * k) | ||
| return (hits.sum() / (len(I) * k), msg) | ||
|
|
||
|
|
||
| def add_details_from_tira(fn, row): | ||
| software_details = Path(fn).parent.parent / "execution-details.json" | ||
|
|
||
| if software_details.is_file(): | ||
| software_details = json.loads(software_details.read_text())["system"] | ||
| to_delete = ["docker_software_id", "user_image_name", "tira_image_name", "cache_behaviour", "task_id", "paper_link", "input_docker_software", "link_code", "mount_hf_model", "workflow_configuration", "forward_environment_variable", "input_docker_software_id", "input_upload_id", "ir_re_ranker", "ir_re_ranking_input", "previous_stages", "tira_image_workdir"] | ||
| for i in to_delete: | ||
| del software_details[i] | ||
| software_details = json.dumps(software_details) | ||
| else: | ||
| software_details = None | ||
|
|
||
| ir_metadata = Path(fn).parent / ".tracking-results.yml" | ||
| if ir_metadata.is_file(): | ||
| import yaml | ||
| ir_metadata = yaml.safe_load(ir_metadata.read_text()) | ||
| tmp = {} | ||
| for i in ["cpu", "ram"]: | ||
| for j in ["system", "process"]: | ||
| tmp[f"{i}-{j}-max"] = ir_metadata["resources"][i][f"used {j}"]["max"] | ||
|
|
||
| tmp["process-wallclock"] = ir_metadata["resources"]["runtime"]["wallclock"] | ||
| ir_metadata = tmp | ||
| else: | ||
| ir_metadata = {} | ||
|
|
||
| row["software"] = software_details | ||
| if ir_metadata: | ||
| for k, v in ir_metadata.items(): | ||
| row[k] = v | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
|
|
@@ -67,19 +130,49 @@ def get_recall(I, gt, k): | |
| help="directory in which results are stored", | ||
| default="results", | ||
| ) | ||
| parser.add_argument( | ||
| "--dataset", | ||
| help="the dataset to evaluate (otherwise look into the .h5 attributes)", | ||
| default=None, | ||
| ) | ||
| parser.add_argument( | ||
| "--task", | ||
| help="the task to evaluate (otherwise look into the .h5 attributes)", | ||
| default=None, | ||
| ) | ||
| parser.add_argument( | ||
| "--cache", | ||
| help="cache file for faster ", | ||
| default=None, | ||
| ) | ||
| parser.add_argument("csvfile") | ||
| args = parser.parse_args() | ||
|
|
||
| gt_cache = {} # (dataset, task) -> gt_I array | ||
| columns = ["dataset", "task", "algo", "buildtime", "querytime", "params", "recall"] | ||
| row_cache = {} | ||
|
|
||
| if args.cache and Path(args.cache).is_file(): | ||
| with open(args.cache, "r") as f: | ||
| for l in f: | ||
| try: | ||
| l = json.loads(l) | ||
| row_cache[l["fn"]] = l["row"] | ||
| except: | ||
| pass | ||
| print(f"Have {len(row_cache)} lines in cache {args.cache}.") | ||
|
|
||
| with open(args.csvfile, "w", newline="") as csvfile: | ||
| writer = csv.DictWriter(csvfile, fieldnames=columns, extrasaction="ignore") | ||
| writer.writeheader() | ||
|
|
||
| for fn, attrs, knns in get_all_results(args.results): | ||
| dataset = attrs["dataset"] | ||
| task = attrs["task"] | ||
| for fn in tqdm(list(get_all_results(args.results))): | ||
| if fn in row_cache: | ||
| writer.writerow(row_cache[fn]) | ||
| continue | ||
| attrs, knns = load_results(fn) | ||
| dataset = attrs["dataset"] if not args.dataset else args.dataset | ||
| task = attrs["task"] if not args.task else args.task | ||
|
|
||
| if dataset not in DATASETS or task not in DATASETS[dataset]: | ||
| print(f"Skipping {fn}: unknown dataset={dataset!r} task={task!r}") | ||
|
|
@@ -99,6 +192,13 @@ def get_recall(I, gt, k): | |
| k = DATASETS[dataset][task]["k"] | ||
| recall = get_recall(knns, gt_I, k) | ||
| row = dict(attrs) | ||
| row["recall"] = recall | ||
| print(dataset, task, attrs.get("algo"), attrs.get("params"), "=>", recall) | ||
| row["recall"] = recall[0] | ||
| row["recall_description"] = recall[1] | ||
| add_details_from_tira(fn, row) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be guarded by a flag parsed as command line argument |
||
|
|
||
| writer.writerow(row) | ||
|
|
||
| if args.cache: | ||
| with open(args.cache, "a+") as f: | ||
| f.write(json.dumps({"fn": fn, "row": row}) + "\n") | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't that rather helpful? :-) to see the progress?