From 2cebcb315008ce2a0c8f1548e87b438623785add Mon Sep 17 00:00:00 2001 From: Benjamin Bossan Date: Thu, 9 Mar 2023 12:57:48 +0100 Subject: [PATCH 1/4] Resolves #292, supersedes #296 As discussed in #296, instead of dealing with the issue what should happen if the user sets model_card.model_diagram = False after initializing with True, just remove the attribute from the Card class. This way, there cannot be the false impression that the diagram can be removed from the card by setting the attribute to False. If users really need to remove the model diagram after enabling it at first, they can just delete the corresponding section (or make it invisible). On top of that, a feature was added that passing a str to model_diagram will use that str as the section name. Previously, always the default section would be used (for skops template) or no section added (for custom templates). --- skops/card/_model_card.py | 47 +++++++++++++++++++++++++---------- skops/card/tests/test_card.py | 33 ++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/skops/card/_model_card.py b/skops/card/_model_card.py index 15a8fe04..e1b1c41c 100644 --- a/skops/card/_model_card.py +++ b/skops/card/_model_card.py @@ -380,8 +380,15 @@ class Card: ``Path``/``str`` of the model or the actual model instance that will be documented. If a ``Path`` or ``str`` is provided, model will be loaded. - model_diagram: bool, default=True - Set to True if model diagram should be plotted in the card. + model_diagram: bool or str, default=True + If using the skops template, setting this to ``True`` will add the model + diagram, as generated by sckit-learn, to the default section. Passing a + string to ``model_diagram`` will instead use that string as the section + name for the diagram. + + If using a non-skops template, the model diagram cannot be added + automatically unless a string is passed. It can, however, always be + added later using :meth:`Card.add_model_plot`. metadata: ModelCardData, optional :class:`huggingface_hub.ModelCardData` object. The contents of this @@ -487,7 +494,6 @@ def __init__( trusted: bool = False, ) -> None: self.model = model - self.model_diagram = model_diagram self.metadata = metadata or ModelCardData() self.template = template self.trusted = trusted @@ -495,13 +501,20 @@ def __init__( self._data: dict[str, Section] = {} self._metrics: dict[str, str | float | int] = {} - self._populate_template() + self._populate_template(model_diagram=model_diagram) - def _populate_template(self): - """If initialized with a template, use it to populate the card.""" - if not self.template: - return + def _populate_template(self, model_diagram: bool | str): + """If initialized with a template, use it to populate the card. + + Parameters + ---------- + model_diagram: bool or str + If model_diagram is truthy and str, use str as section name. If + model_diagram is True, use default section for default template or + ignore for non-default template. If model_diagram is False, don't + add it. + """ if isinstance(self.template, str) and (self.template not in VALID_TEMPLATES): valid_templates = ", ".join(f"'{val}'" for val in sorted(VALID_TEMPLATES)) msg = ( @@ -510,15 +523,26 @@ def _populate_template(self): ) raise ValueError(msg) + # default template if self.template == Templates.skops.value: self.add(**SKOPS_TEMPLATE) # for the skops template, automatically add some default sections - self.add_model_plot() self.add_hyperparams() self.add_get_started_code() - elif isinstance(self.template, Mapping): + if model_diagram: + if isinstance(model_diagram, str): + self.add_model_plot(section=model_diagram) + else: + self.add_model_plot() + return + + # non-default template + if isinstance(self.template, Mapping): self.add(**self.template) + if model_diagram and isinstance(model_diagram, str): + self.add_model_plot(section=model_diagram) + def get_model(self) -> Any: """Returns sklearn estimator object. @@ -789,9 +813,6 @@ def add_model_plot( self : object Card object. """ - if not self.model_diagram: - return self - if section is None: if self.template == Templates.skops.value: section = "Model description/Training Procedure/Model Plot" diff --git a/skops/card/tests/test_card.py b/skops/card/tests/test_card.py index 0a97a303..1058d115 100644 --- a/skops/card/tests/test_card.py +++ b/skops/card/tests/test_card.py @@ -177,6 +177,26 @@ def test_model_diagram_false(self): ).content assert result == "The model plot is below." + def test_model_diagram_str(self): + # if passing a str, use that as the section name + model = fit_model() + other_section_name = "Here is the model diagram" + model_card = Card(model, model_diagram=other_section_name) + + # first check that default section only contains placeholder + result = model_card.select( + "Model description/Training Procedure/Model Plot" + ).format() + assert result == "The model plot is below." + + # now check that the actual model diagram is in the other section + result = model_card.select(other_section_name).format() + assert result.startswith("The model plot is below.\n\n