forked from enviPath/enviPy
[Fix] Export IUCLID Properties, Show Model Params and Statistics, Adjust Batch Prediction Settings (#434)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#434
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import enum
|
import enum
|
||||||
|
from typing import Any, Dict
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
from envipy_additional_information import EnviPyModel
|
from envipy_additional_information import EnviPyModel
|
||||||
@ -69,6 +70,12 @@ class Plugin(ABC):
|
|||||||
|
|
||||||
|
|
||||||
class Property(Plugin):
|
class Property(Plugin):
|
||||||
|
def parameters(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Returns the parameters of the PropertyPlugin.
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def requires_rule_packages(cls) -> bool:
|
def requires_rule_packages(cls) -> bool:
|
||||||
@ -300,6 +307,12 @@ class Classifier(Plugin):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def parameters(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Returns the parameters of the ClassifierPlugin.
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def build(self, eP: EnviPyDTO, *args, **kwargs) -> BuildResult | None:
|
def build(self, eP: EnviPyDTO, *args, **kwargs) -> BuildResult | None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -56,7 +56,7 @@ def get_pathway_for_iuclid_export(user, pathway_uuid: UUID) -> PathwayExportDTO:
|
|||||||
|
|
||||||
ai_for_node = []
|
ai_for_node = []
|
||||||
scenario_entries: list[PathwayScenarioDTO] = []
|
scenario_entries: list[PathwayScenarioDTO] = []
|
||||||
for scenario in sorted(node.scenarios.all(), key=lambda item: item.pk):
|
for scenario in sorted(node.get_scenarios(), key=lambda item: item.pk):
|
||||||
ai_for_scenario = list(scenario.get_additional_information(direct_only=True))
|
ai_for_scenario = list(scenario.get_additional_information(direct_only=True))
|
||||||
ai_for_node.extend(ai_for_scenario)
|
ai_for_node.extend(ai_for_scenario)
|
||||||
scenario_entries.append(
|
scenario_entries.append(
|
||||||
|
|||||||
@ -2162,7 +2162,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
|||||||
|
|
||||||
row += [cs.smiles, cs.get_name(), n.depth]
|
row += [cs.smiles, cs.get_name(), n.depth]
|
||||||
|
|
||||||
edges = self.edges.filter(end_nodes__in=[n])
|
edges = self.edges.filter(end_nodes=n)
|
||||||
if len(edges):
|
if len(edges):
|
||||||
for e in edges:
|
for e in edges:
|
||||||
_row = row.copy()
|
_row = row.copy()
|
||||||
@ -2847,6 +2847,58 @@ class PackageBasedModel(EPModel):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def parameters(self):
|
||||||
|
params = {
|
||||||
|
"Model Evaluation Threshold": f"{self.threshold:.2f}",
|
||||||
|
"Multi Gen Evaluation": "Yes" if self.multigen_eval else "No",
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.app_domain:
|
||||||
|
params["Applicability Domain Num Neighbors"] = f"{self.app_domain.num_neighbours:.2f}"
|
||||||
|
params["Applicability Domain Reliability Threshold"] = (
|
||||||
|
f"{self.app_domain.reliability_threshold:.2f}"
|
||||||
|
)
|
||||||
|
params["Applicability Domain Local Compatibility Threshold"] = (
|
||||||
|
f"{self.app_domain.local_compatibilty_threshold:.2f}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
def statistics(self):
|
||||||
|
from sklearn.metrics import auc
|
||||||
|
|
||||||
|
recall = list(self.eval_results["average_recall_per_threshold"].values())
|
||||||
|
precision = list(self.eval_results["average_precision_per_threshold"].values())
|
||||||
|
mg_recall = list(
|
||||||
|
self.eval_results.get("multigen_average_recall_per_threshold", {}).values()
|
||||||
|
)
|
||||||
|
mg_precision = list(
|
||||||
|
self.eval_results.get("multigen_average_precision_per_threshold", {}).values()
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"accuracy": [
|
||||||
|
self.eval_results["average_accuracy"],
|
||||||
|
self.eval_results.get("multigen_average_accuracy"),
|
||||||
|
],
|
||||||
|
"precision": [
|
||||||
|
self.eval_results["average_precision_per_threshold"][f"{self.threshold:.2f}"],
|
||||||
|
self.eval_results.get("multigen_average_precision_per_threshold", {}).get(
|
||||||
|
f"{self.threshold:.2f}"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"recall": [
|
||||||
|
self.eval_results["average_recall_per_threshold"][f"{self.threshold:.2f}"],
|
||||||
|
self.eval_results.get("multigen_average_recall_per_threshold", {}).get(
|
||||||
|
f"{self.threshold:.2f}"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"Area under PR Curve": [
|
||||||
|
auc(recall, precision),
|
||||||
|
auc(mg_recall, mg_precision) if self.multigen_eval else None,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def applicable_rules(self) -> List["Rule"]:
|
def applicable_rules(self) -> List["Rule"]:
|
||||||
"""
|
"""
|
||||||
@ -3027,8 +3079,6 @@ class PackageBasedModel(EPModel):
|
|||||||
thresholds.append(np.float64(threshold))
|
thresholds.append(np.float64(threshold))
|
||||||
thresholds.sort()
|
thresholds.sort()
|
||||||
|
|
||||||
logger.info(f"Thresholds: {thresholds}")
|
|
||||||
|
|
||||||
precision = {f"{t:.2f}": [] for t in thresholds}
|
precision = {f"{t:.2f}": [] for t in thresholds}
|
||||||
recall = {f"{t:.2f}": [] for t in thresholds}
|
recall = {f"{t:.2f}": [] for t in thresholds}
|
||||||
|
|
||||||
@ -3077,14 +3127,17 @@ class PackageBasedModel(EPModel):
|
|||||||
for t in thresholds:
|
for t in thresholds:
|
||||||
for true, pred in zip(pathways, pred_pathways):
|
for true, pred in zip(pathways, pred_pathways):
|
||||||
acc, pre, rec = multigen_eval(true, pred, t)
|
acc, pre, rec = multigen_eval(true, pred, t)
|
||||||
if abs(t - threshold) < 0.01:
|
|
||||||
mg_acc = acc
|
if f"{t:.2f}" == f"{threshold:.2f}":
|
||||||
|
mg_acc += acc
|
||||||
|
|
||||||
precision[f"{t:.2f}"].append(pre)
|
precision[f"{t:.2f}"].append(pre)
|
||||||
recall[f"{t:.2f}"].append(rec)
|
recall[f"{t:.2f}"].append(rec)
|
||||||
|
|
||||||
|
avg_mg_acc = mg_acc / len(root_compounds)
|
||||||
precision = {k: sum(v) / len(v) if len(v) > 0 else 0 for k, v in precision.items()}
|
precision = {k: sum(v) / len(v) if len(v) > 0 else 0 for k, v in precision.items()}
|
||||||
recall = {k: sum(v) / len(v) if len(v) > 0 else 0 for k, v in recall.items()}
|
recall = {k: sum(v) / len(v) if len(v) > 0 else 0 for k, v in recall.items()}
|
||||||
return mg_acc, precision, recall
|
return avg_mg_acc, precision, recall
|
||||||
|
|
||||||
# If there are eval packages perform single generation evaluation on them instead of random splits
|
# If there are eval packages perform single generation evaluation on them instead of random splits
|
||||||
if self.eval_packages.count() > 0:
|
if self.eval_packages.count() > 0:
|
||||||
@ -4216,6 +4269,9 @@ class ClassifierPluginModel(PackageBasedModel):
|
|||||||
instance = impl(conf)
|
instance = impl(conf)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
def parameters(self):
|
||||||
|
return self.instance().parameters()
|
||||||
|
|
||||||
def build_dataset(self):
|
def build_dataset(self):
|
||||||
"""
|
"""
|
||||||
Required by general model contract but actual implementation resides in plugin.
|
Required by general model contract but actual implementation resides in plugin.
|
||||||
@ -4436,6 +4492,9 @@ class PropertyPluginModel(PackageBasedModel):
|
|||||||
instance = impl()
|
instance = impl()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
def parameters(self):
|
||||||
|
return self.instance().parameters()
|
||||||
|
|
||||||
def build_dataset(self):
|
def build_dataset(self):
|
||||||
"""
|
"""
|
||||||
Required by general model contract but actual implementation resides in plugin.
|
Required by general model contract but actual implementation resides in plugin.
|
||||||
|
|||||||
@ -477,8 +477,7 @@ def batch_predict(
|
|||||||
limit=None,
|
limit=None,
|
||||||
setting_overrides={
|
setting_overrides={
|
||||||
"max_nodes": num_tps,
|
"max_nodes": num_tps,
|
||||||
"max_depth": num_tps,
|
"model_threshold": 0.0,
|
||||||
"model_threshold": 0.001,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -351,7 +351,8 @@ class PathwayMapper:
|
|||||||
|
|
||||||
props = SoilPropertiesData()
|
props = SoilPropertiesData()
|
||||||
|
|
||||||
for ai in ai_list:
|
for ai_obj in ai_list:
|
||||||
|
ai = ai_obj.get()
|
||||||
if isinstance(ai, SoilTexture1) and props.soil_type is None:
|
if isinstance(ai, SoilTexture1) and props.soil_type is None:
|
||||||
props.soil_type = ai.type.value
|
props.soil_type = ai.type.value
|
||||||
elif isinstance(ai, SoilTexture2):
|
elif isinstance(ai, SoilTexture2):
|
||||||
|
|||||||
@ -117,6 +117,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if model.parameters %}
|
||||||
|
<!-- Model Parameters Panel -->
|
||||||
|
<div class="collapse-arrow bg-base-200 collapse">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-title text-xl font-medium">Model Parameters</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div
|
||||||
|
id="model-stats"
|
||||||
|
class="overflow-x-auto rounded-box shadow-md bg-base-100"
|
||||||
|
>
|
||||||
|
<table class="table table-fixed w-full">
|
||||||
|
<thead class="text-base">
|
||||||
|
<tr>
|
||||||
|
<th class="w-3/5">Parameter</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for param, value in model.parameters.items %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ param }}</td>
|
||||||
|
<td>{{ value }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block usemodel %}
|
{% block usemodel %}
|
||||||
|
|||||||
@ -313,6 +313,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Model Statistics Panel -->
|
||||||
|
<div class="collapse-arrow bg-base-200 collapse">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-title text-xl font-medium">Model Statistics for threshold {{ model.threshold }}</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div
|
||||||
|
id="model-stats"
|
||||||
|
class="overflow-x-auto rounded-box shadow-md bg-base-100"
|
||||||
|
>
|
||||||
|
<table class="table table-fixed w-full">
|
||||||
|
<thead class="text-base">
|
||||||
|
<tr>
|
||||||
|
<th class="w-1/5">Metric</th>
|
||||||
|
<th>Single Gen Value</th>
|
||||||
|
{% if model.multigen_eval %}
|
||||||
|
<th>Multi Gen Value</th>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for metric, value in model.statistics.items %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ metric|upper }}</td>
|
||||||
|
<td>{{ value.0|floatformat:3 }}</td>
|
||||||
|
{% if model.multigen_eval %}
|
||||||
|
<td>{{ value.1|floatformat:3 }}</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script>
|
<script>
|
||||||
function makeChart(selector, data) {
|
function makeChart(selector, data) {
|
||||||
|
|||||||
@ -535,7 +535,7 @@
|
|||||||
<input type="checkbox" />
|
<input type="checkbox" />
|
||||||
<div class="collapse-title text-xl font-medium">Setting</div>
|
<div class="collapse-title text-xl font-medium">Setting</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
{% with setting_to_render=pathway.setting can_be_default=False %}
|
{% with setting_to_render=pathway.setting_with_overrides can_be_default=False %}
|
||||||
{% include "objects/setting_template.html" %}
|
{% include "objects/setting_template.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -95,21 +95,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Other Prediction Settings -->
|
<!-- Other Prediction Settings -->
|
||||||
<div class="collapse-arrow bg-base-200 collapse">
|
{% if meta.available_settings|length > 1 %}
|
||||||
<input type="checkbox" />
|
<div class="collapse-arrow bg-base-200 collapse">
|
||||||
<div class="collapse-title text-xl font-medium">
|
<input type="checkbox" />
|
||||||
Other Prediction Settings
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Other Prediction Settings
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content space-y-3">
|
||||||
|
{% for setting in meta.available_settings %}
|
||||||
|
{% if setting != user.default_setting %}
|
||||||
|
{% with setting_to_render=setting can_be_default=True %}
|
||||||
|
{% include "objects/setting_template.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content space-y-3">
|
{% endif %}
|
||||||
{% for setting in meta.available_settings %}
|
|
||||||
{% if setting != user.default_setting %}
|
|
||||||
{% with setting_to_render=setting can_be_default=True %}
|
|
||||||
{% include "objects/setting_template.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -58,7 +58,7 @@ class MultiGenTest(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
pw.setting_with_overrides.max_depth,
|
pw.setting_with_overrides.max_depth,
|
||||||
f"{num_tps} (this is an override for this particular pathway)",
|
5,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
pw.setting_with_overrides.max_nodes,
|
pw.setting_with_overrides.max_nodes,
|
||||||
|
|||||||
@ -134,7 +134,7 @@ class EnzymeExportSchema(RefEnzymeExportSchema):
|
|||||||
|
|
||||||
|
|
||||||
class EnzymeRuleExportSchema(RefRuleExportSchema):
|
class EnzymeRuleExportSchema(RefRuleExportSchema):
|
||||||
enzymes: List[EnzymeExportSchema] | None = None
|
enzymes: List[EnzymeExportSchema] = []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_enzymes(obj):
|
def resolve_enzymes(obj):
|
||||||
|
|||||||
Reference in New Issue
Block a user