Added model evaluation script
This commit is contained in:
parent
f61d88e0d8
commit
1ffedb5ddc
316
teng-ml/find_best_model.py
Normal file
316
teng-ml/find_best_model.py
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
from os import path, listdir
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
from sys import exit, argv
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if __package__ is None:
|
||||||
|
# make relative imports work as described here: https://peps.python.org/pep-0366/#proposed-change
|
||||||
|
__package__ = "teng-ml"
|
||||||
|
import sys
|
||||||
|
from os import path
|
||||||
|
filepath = path.realpath(path.abspath(__file__))
|
||||||
|
sys.path.insert(0, path.dirname(path.dirname(filepath)))
|
||||||
|
|
||||||
|
from .tracker.epoch_tracker import EpochTracker
|
||||||
|
from .util.settings import MLSettings
|
||||||
|
from .util import model_io as mio
|
||||||
|
from .util.string import cleanup_str, fill_and_center
|
||||||
|
|
||||||
|
|
||||||
|
cache = {}
|
||||||
|
|
||||||
|
#
|
||||||
|
# TOOLS
|
||||||
|
#
|
||||||
|
def get_model_dirs(models_dir):
|
||||||
|
"""
|
||||||
|
return all model_dirs, relative to models_dir
|
||||||
|
"""
|
||||||
|
if "model_dirs" in cache: return cache["model_dirs"].copy()
|
||||||
|
paths = listdir(models_dir)
|
||||||
|
model_dirs = []
|
||||||
|
for p in paths:
|
||||||
|
if not path.isdir(f"{models_dir}/{p}"): continue
|
||||||
|
if not path.isfile(f"{models_dir}/{p}/settings.pkl"): continue
|
||||||
|
if not path.isfile(f"{models_dir}/{p}/tracker_training.pkl"): continue
|
||||||
|
if not path.isfile(f"{models_dir}/{p}/tracker_validation.pkl"): continue
|
||||||
|
if not path.isfile(f"{models_dir}/{p}/model.pkl"): continue
|
||||||
|
model_dirs.append(f"{models_dir}/{p}")
|
||||||
|
cache["model_dirs"] = model_dirs.copy()
|
||||||
|
return model_dirs
|
||||||
|
|
||||||
|
|
||||||
|
def resave_images_svg(model_dirs):
|
||||||
|
"""
|
||||||
|
open all trackers and save all plots as svg
|
||||||
|
"""
|
||||||
|
for model_dir in model_dirs:
|
||||||
|
val_tracker: EpochTracker = mio.load_tracker_validation(model_dir)
|
||||||
|
fig, _ = val_tracker.plot_predictions("Validation: Predictions", model_dir=model_dir, name="img_validation_predictions")
|
||||||
|
train_tracker: EpochTracker = mio.load_tracker_training(model_dir)
|
||||||
|
fig, _ = train_tracker.plot_predictions("Training: Predictions", model_dir=model_dir, name="img_training_predictions")
|
||||||
|
fig, _ = train_tracker.plot_training(model_dir=model_dir)
|
||||||
|
plt.close('all')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# MODEL RANKING
|
||||||
|
#
|
||||||
|
def get_model_info_md(model_dir):
|
||||||
|
st: MLSettings = mio.load_settings(model_dir)
|
||||||
|
validation_tracker = mio.load_tracker_validation(model_dir)
|
||||||
|
training_tracker = mio.load_tracker_training(model_dir)
|
||||||
|
|
||||||
|
s = f"""Model {model_dir[model_dir.rfind('/')+1:]}
|
||||||
|
Model parameters:
|
||||||
|
- num_features = {st.num_features}
|
||||||
|
- num_layers = {st.num_layers}
|
||||||
|
- hidden_size = {st.hidden_size}
|
||||||
|
- bidirectional = {st.bidirectional}
|
||||||
|
Training data:
|
||||||
|
- transforms = {st.transforms}
|
||||||
|
- splitter = {st.splitter}
|
||||||
|
- labels = {st.labels}
|
||||||
|
Training info:
|
||||||
|
- optimizer = {cleanup_str(st.optimizer)}
|
||||||
|
- scheduler = {cleanup_str(st.scheduler)}
|
||||||
|
- loss_func = {st.loss_func}
|
||||||
|
- num_epochs = {st.num_epochs}
|
||||||
|
- batch_size = {st.batch_size}
|
||||||
|
- n_predictions = {np.sum(training_tracker.get_count_per_label())}
|
||||||
|
- final accuracy = {training_tracker.accuracies[-1]}
|
||||||
|
- highest accuracy = {np.max(training_tracker.accuracies)}
|
||||||
|
Validation info:
|
||||||
|
- n_predictions = {np.sum(validation_tracker.get_count_per_label())}
|
||||||
|
- accuracy = {validation_tracker.accuracies[-1]}
|
||||||
|
"""
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def write_model_info(model_dir, model_info=None):
|
||||||
|
if model_info is None: model_info = get_model_info_md(model_dir)
|
||||||
|
with open(f"{model_dir}/model_info.md", "w") as file:
|
||||||
|
file.write(model_info)
|
||||||
|
|
||||||
|
|
||||||
|
def get_model_ranking(model_dirs):
|
||||||
|
if "model_ranking" in cache: return cache["model_ranking"].copy()
|
||||||
|
model_ranking = [] # model, (model_dir | validation accuracy)
|
||||||
|
for model_dir in model_dirs:
|
||||||
|
model_ranking.append((model_dir, mio.load_tracker_validation(model_dir).accuracies[-1]))
|
||||||
|
model_ranking.sort(key=lambda t: t[1]) # sort accuracy
|
||||||
|
model_ranking.reverse() # best to worst
|
||||||
|
cache["model_ranking"] = model_ranking.copy()
|
||||||
|
return model_ranking
|
||||||
|
|
||||||
|
def get_model_ranking_md(model_dirs):
|
||||||
|
model_ranking = get_model_ranking(model_dirs)
|
||||||
|
ranking_md = ""
|
||||||
|
for i in range(len(model_ranking)):
|
||||||
|
model_dir = model_ranking[i][0]
|
||||||
|
model_name = model_dir[model_dir.rfind("/")+1:]
|
||||||
|
ranking_md += f"{i+1:3}. Model=`{model_name}`, Validation accuaracy={round(model_ranking[i][1], 2):.2f}%\n"
|
||||||
|
return ranking_md
|
||||||
|
|
||||||
|
#
|
||||||
|
# SETTINGS RANKING
|
||||||
|
#
|
||||||
|
def get_settings_ranking(model_dirs, use_ranking_instead_of_accuracy=False):
|
||||||
|
"""
|
||||||
|
load the settings for each model and score them based on the performance of the model
|
||||||
|
This only works when all settings were the same number of times
|
||||||
|
(Example: 2 batch sizes x and y have to both to be used z times for the ranking to make sense)
|
||||||
|
"""
|
||||||
|
if "settings_ranking" in cache: return cache["settings_ranking"].copy()
|
||||||
|
settings_ranking = {} # parameter name: param_value: score
|
||||||
|
|
||||||
|
model_ranking = get_model_ranking(model_dirs)
|
||||||
|
model_ranking.reverse() # worst to best
|
||||||
|
def score_ranking_based(i, param_name, param_value):
|
||||||
|
"""
|
||||||
|
score settings depending on the ranking of the model
|
||||||
|
eg: best of 32 models has batch_size 10 -> batch_size 10 gets 32 points
|
||||||
|
"""
|
||||||
|
param_value = cleanup_str(param_value)
|
||||||
|
if not param_name in settings_ranking.keys():
|
||||||
|
settings_ranking[param_name] = {}
|
||||||
|
if not param_value in settings_ranking[param_name].keys():
|
||||||
|
settings_ranking[param_name][param_value] = 0
|
||||||
|
settings_ranking[param_name][param_value] += i # i+1 is reverse place in the ranking, worst model is at i=0
|
||||||
|
|
||||||
|
def score_accuracy_based(i, param_name, param_value):
|
||||||
|
"""
|
||||||
|
score settings depending on the accuracy of the model
|
||||||
|
eg: models has batch_size 10 and accuracy 63% -> batch_size 10 gets 63 points
|
||||||
|
"""
|
||||||
|
param_value = cleanup_str(param_value)
|
||||||
|
if not param_name in settings_ranking.keys():
|
||||||
|
settings_ranking[param_name] = {}
|
||||||
|
if not param_value in settings_ranking[param_name].keys():
|
||||||
|
settings_ranking[param_name][param_value] = 0
|
||||||
|
settings_ranking[param_name][param_value] += int(model_ranking[i][1]) # accuracy
|
||||||
|
|
||||||
|
if use_ranking_instead_of_accuracy:
|
||||||
|
score = lambda i, name, val : score_ranking_based(i, name, val)
|
||||||
|
else:
|
||||||
|
score = lambda i, name, val : score_accuracy_based(i, name, val)
|
||||||
|
for i in range(len(model_ranking)):
|
||||||
|
st = mio.load_settings(model_ranking[i][0])
|
||||||
|
score(i, "num_features", st.num_features)
|
||||||
|
score(i, "num_layers", st.num_layers)
|
||||||
|
score(i, "hidden_size", st.hidden_size)
|
||||||
|
score(i, "num_epochs", st.num_epochs)
|
||||||
|
score(i, "bidirectional", st.bidirectional)
|
||||||
|
score(i, "optimizer", st.optimizer)
|
||||||
|
score(i, "scheduler", st.scheduler)
|
||||||
|
score(i, "loss_func", st.loss_func)
|
||||||
|
score(i, "transforms", st.transforms)
|
||||||
|
score(i, "splitter", st.splitter)
|
||||||
|
score(i, "batch_size", st.batch_size)
|
||||||
|
# remove parameters with only one value
|
||||||
|
settings_ranking = { k: v for k, v in settings_ranking.items() if len(v) > 1 }
|
||||||
|
cache["settings_ranking"] = settings_ranking.copy()
|
||||||
|
return settings_ranking
|
||||||
|
|
||||||
|
def get_settings_ranking_md(model_dirs):
|
||||||
|
"""
|
||||||
|
turn the scores dict from rank_settings into a markdown string
|
||||||
|
"""
|
||||||
|
settings_ranking = get_settings_ranking(model_dirs)
|
||||||
|
s = ""
|
||||||
|
for param_name, d in settings_ranking.items():
|
||||||
|
s += f"- {param_name}:\n"
|
||||||
|
sorted_scores = sorted(d.items(), key=lambda x: x[1], reverse=True)
|
||||||
|
for i in range(len(sorted_scores)):
|
||||||
|
param_value, score = sorted_scores[i]
|
||||||
|
s += f"\t{i+1}. `{param_value}` ({score} points)\n"
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def interactive_model_inspector(models_dir: str):
|
||||||
|
model_dirs = get_model_dirs(models_dir)
|
||||||
|
model_dirs.sort()
|
||||||
|
model_names = [ mdir[mdir.rfind('/')+1:] for mdir in model_dirs ]
|
||||||
|
|
||||||
|
def print_options():
|
||||||
|
s = fill_and_center("Interactive Model Inspector") + "\n"
|
||||||
|
for i in range(len(model_names)):
|
||||||
|
s += f"{i+1:02}: {model_names[i]}\n"
|
||||||
|
s += """ ---
|
||||||
|
x: print model info for x. listed model (1-based)
|
||||||
|
x.: print model info for x. ranked model (1-based)
|
||||||
|
w: write last model info
|
||||||
|
wa: write info for all listed models
|
||||||
|
q: quit
|
||||||
|
*: name of model or path to model directory. If not found, reprint list."""
|
||||||
|
print(s)
|
||||||
|
last_model_info = None
|
||||||
|
last_model_dir = None
|
||||||
|
|
||||||
|
def print_model_info(model_dir):
|
||||||
|
last_model_dir = model_dir
|
||||||
|
last_model_info = get_model_info_md(last_model_dir)
|
||||||
|
print(last_model_info)
|
||||||
|
print_options()
|
||||||
|
loop = True
|
||||||
|
try:
|
||||||
|
while loop:
|
||||||
|
answer = input("> ")
|
||||||
|
if len(answer) == 0: continue
|
||||||
|
try: # if x -> take x. from listed models
|
||||||
|
i = int(answer)
|
||||||
|
if 0 < i and i <= len(model_dirs):
|
||||||
|
|
||||||
|
print_model_info(model_dirs[i-1])
|
||||||
|
continue
|
||||||
|
except ValueError: pass
|
||||||
|
if answer.endswith('.'): # if x. -> take x. from model ranking
|
||||||
|
try:
|
||||||
|
i = int(answer[:-1])
|
||||||
|
if 0 < i and i <= len(model_dirs):
|
||||||
|
model_ranking = get_model_ranking(model_dirs)
|
||||||
|
print_model_info(model_ranking[i-1][0])
|
||||||
|
continue
|
||||||
|
except ValueError: pass
|
||||||
|
|
||||||
|
elif answer == "w":
|
||||||
|
if last_model_info is None:
|
||||||
|
print("Print a model info first.")
|
||||||
|
continue
|
||||||
|
write_model_info(last_model_dir, last_model_info)
|
||||||
|
elif answer == "wa":
|
||||||
|
for model_dir in model_dirs:
|
||||||
|
write_model_info(model_dir)
|
||||||
|
elif answer == "q":
|
||||||
|
loop = False
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if path.isdir(answer): # if model dir
|
||||||
|
print_model_info(answer)
|
||||||
|
elif path.isdir(f"{models_dir}/{answer}"): # if model name
|
||||||
|
print_model_info(f"{models_dir}/{answer}")
|
||||||
|
else:
|
||||||
|
print(f"'{answer}' is not a model name in {models_dir} or path to a model directory.")
|
||||||
|
print_options()
|
||||||
|
except KeyboardInterrupt: # if <C-C>
|
||||||
|
pass
|
||||||
|
except EOFError: # if <C-D>
|
||||||
|
exit(0)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(argv) != 2:
|
||||||
|
print(f"Exactly one argument (models directory) is required, but got {len(argv)-1}.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# models_dir = "/home/matth/Uni/TENG/models_phase_2" # where to save models, settings and results
|
||||||
|
models_dir = path.abspath(path.expanduser(argv[1]))
|
||||||
|
|
||||||
|
model_dirs = get_model_dirs(models_dir)
|
||||||
|
|
||||||
|
def save_model_ranking():
|
||||||
|
model_ranking = get_model_ranking_md(model_dirs)
|
||||||
|
with open(f"{models_dir}/ranking_models.md", "w") as file:
|
||||||
|
file.write(model_ranking)
|
||||||
|
|
||||||
|
def save_settings_ranking():
|
||||||
|
scores = get_settings_ranking(model_dirs)
|
||||||
|
with open(f"{models_dir}/ranking_settings.md", "w") as file:
|
||||||
|
file.write(get_settings_ranking_md(scores))
|
||||||
|
|
||||||
|
|
||||||
|
# if the functions return True, the options are printed again
|
||||||
|
options = {
|
||||||
|
'1': ("Print model ranking", lambda: print(get_model_ranking_md(model_dirs))),
|
||||||
|
'2': ("Save model ranking", save_model_ranking),
|
||||||
|
'3': ("Print settings ranking", lambda: print(get_settings_ranking_md(model_dirs))),
|
||||||
|
'4': ("Save settings ranking", save_settings_ranking),
|
||||||
|
'5': ("Interactive model inspector", lambda: interactive_model_inspector(models_dir)),
|
||||||
|
'6': ("Resave all images", lambda: resave_images_svg(model_dirs)),
|
||||||
|
'q': ("quit", exit)
|
||||||
|
}
|
||||||
|
|
||||||
|
def print_options():
|
||||||
|
print(fill_and_center("Model Evaluator"))
|
||||||
|
for op, (name, _) in options.items():
|
||||||
|
print(f"{op:4}: {name}")
|
||||||
|
print(f"Using models directory '{models_dir}', which contains {len(model_dirs)} models")
|
||||||
|
print_options()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
answer = input("> ")
|
||||||
|
if answer in options.keys():
|
||||||
|
reprint = options[answer][1]()
|
||||||
|
if reprint == True: print_options()
|
||||||
|
else:
|
||||||
|
print(f"Invalid option: '{answer}'")
|
||||||
|
print_options()
|
||||||
|
except KeyboardInterrupt: pass
|
||||||
|
except EOFError: pass
|
Loading…
Reference in New Issue
Block a user