|
38 | 38 | is_datasets_available,
|
39 | 39 | is_offline_mode,
|
40 | 40 | is_remote_url,
|
| 41 | + is_tf_available, |
41 | 42 | is_tokenizers_available,
|
42 | 43 | is_torch_available,
|
43 | 44 | )
|
@@ -266,11 +267,16 @@ def to_json_file(self, json_file_path):
|
266 | 267 | writer.write(self.to_json_string())
|
267 | 268 |
|
268 | 269 |
|
269 |
| -AUTOGENERATED_COMMENT = """ |
| 270 | +AUTOGENERATED_TRAINER_COMMENT = """ |
270 | 271 | <!-- This model card has been generated automatically according to the information the Trainer had access to. You
|
271 | 272 | should probably proofread and complete it, then remove this comment. -->
|
272 | 273 | """
|
273 | 274 |
|
| 275 | +AUTOGENERATED_KERAS_COMMENT = """ |
| 276 | +<!-- This model card has been generated automatically according to the information Keras had access to. You should |
| 277 | +probably proofread and complete it, then remove this comment. --> |
| 278 | +""" |
| 279 | + |
274 | 280 |
|
275 | 281 | TASK_TAG_TO_NAME_MAPPING = {
|
276 | 282 | "fill-mask": "Masked Language Modeling",
|
@@ -377,6 +383,7 @@ class TrainingSummary:
|
377 | 383 | eval_results: Optional[Dict[str, float]] = None
|
378 | 384 | eval_lines: Optional[List[str]] = None
|
379 | 385 | hyperparameters: Optional[Dict[str, Any]] = None
|
| 386 | + source: Optional[str] = "trainer" |
380 | 387 |
|
381 | 388 | def __post_init__(self):
|
382 | 389 | # Infer default license from the checkpoint used, if possible.
|
@@ -410,15 +417,15 @@ def create_model_index(self, metric_mapping):
|
410 | 417 | task: TASK_TAG_TO_NAME_MAPPING[task] for task in _listify(self.tasks) if task in TASK_TAG_TO_NAME_MAPPING
|
411 | 418 | }
|
412 | 419 |
|
| 420 | + model_index["results"] = [] |
| 421 | + |
413 | 422 | if len(task_mapping) == 0 and len(dataset_mapping) == 0:
|
414 |
| - return model_index |
| 423 | + return [model_index] |
415 | 424 | if len(task_mapping) == 0:
|
416 | 425 | task_mapping = {None: None}
|
417 | 426 | if len(dataset_mapping) == 0:
|
418 | 427 | dataset_mapping = {None: None}
|
419 | 428 |
|
420 |
| - model_index["results"] = [] |
421 |
| - |
422 | 429 | # One entry per dataset and per task
|
423 | 430 | all_possibilities = [(task_tag, ds_tag) for task_tag in task_mapping for ds_tag in dataset_mapping]
|
424 | 431 | for task_tag, ds_tag in all_possibilities:
|
@@ -471,7 +478,10 @@ def to_model_card(self):
|
471 | 478 | model_card = f"---\n{metadata}---\n"
|
472 | 479 |
|
473 | 480 | # Now the model card for realsies.
|
474 |
| - model_card += AUTOGENERATED_COMMENT |
| 481 | + if self.source == "trainer": |
| 482 | + model_card += AUTOGENERATED_TRAINER_COMMENT |
| 483 | + else: |
| 484 | + model_card += AUTOGENERATED_KERAS_COMMENT |
475 | 485 |
|
476 | 486 | model_card += f"\n# {self.model_name}\n\n"
|
477 | 487 |
|
@@ -517,10 +527,15 @@ def to_model_card(self):
|
517 | 527 |
|
518 | 528 | model_card += "\n### Framework versions\n\n"
|
519 | 529 | model_card += f"- Transformers {__version__}\n"
|
520 |
| - if is_torch_available(): |
| 530 | + |
| 531 | + if self.source == "trainer" and is_torch_available(): |
521 | 532 | import torch
|
522 | 533 |
|
523 | 534 | model_card += f"- Pytorch {torch.__version__}\n"
|
| 535 | + elif self.source == "keras" and is_tf_available(): |
| 536 | + import tensorflow as tf |
| 537 | + |
| 538 | + model_card += f"- TensorFlow {tf.__version__}\n" |
524 | 539 | if is_datasets_available():
|
525 | 540 | import datasets
|
526 | 541 |
|
@@ -604,6 +619,113 @@ def from_trainer(
|
604 | 619 | hyperparameters=hyperparameters,
|
605 | 620 | )
|
606 | 621 |
|
| 622 | + @classmethod |
| 623 | + def from_keras( |
| 624 | + cls, |
| 625 | + model, |
| 626 | + model_name, |
| 627 | + keras_history=None, |
| 628 | + language=None, |
| 629 | + license=None, |
| 630 | + tags=None, |
| 631 | + finetuned_from=None, |
| 632 | + tasks=None, |
| 633 | + dataset_tags=None, |
| 634 | + dataset=None, |
| 635 | + dataset_args=None, |
| 636 | + ): |
| 637 | + # Infer default from dataset |
| 638 | + if dataset is not None: |
| 639 | + if is_hf_dataset(dataset) and (dataset_tags is None or dataset_args is None): |
| 640 | + default_tag = dataset.builder_name |
| 641 | + # Those are not real datasets from the Hub so we exclude them. |
| 642 | + if default_tag not in ["csv", "json", "pandas", "parquet", "text"]: |
| 643 | + if dataset_tags is None: |
| 644 | + dataset_tags = [default_tag] |
| 645 | + if dataset_args is None: |
| 646 | + dataset_args = [dataset.config_name] |
| 647 | + |
| 648 | + if dataset is None and dataset_tags is not None: |
| 649 | + dataset = dataset_tags |
| 650 | + |
| 651 | + # Infer default finetuned_from |
| 652 | + if ( |
| 653 | + finetuned_from is None |
| 654 | + and hasattr(model.config, "_name_or_path") |
| 655 | + and not os.path.isdir(model.config._name_or_path) |
| 656 | + ): |
| 657 | + finetuned_from = model.config._name_or_path |
| 658 | + |
| 659 | + # Infer default task tag: |
| 660 | + if tasks is None: |
| 661 | + model_class_name = model.__class__.__name__ |
| 662 | + for task, mapping in TASK_MAPPING.items(): |
| 663 | + if model_class_name in _get_mapping_values(mapping): |
| 664 | + tasks = task |
| 665 | + |
| 666 | + # Add `generated_from_keras_callback` to the tags |
| 667 | + if tags is None: |
| 668 | + tags = ["generated_from_keras_callback"] |
| 669 | + elif isinstance(tags, str) and tags != "generated_from_keras_callback": |
| 670 | + tags = [tags, "generated_from_keras_callback"] |
| 671 | + elif "generated_from_trainer" not in tags: |
| 672 | + tags.append("generated_from_keras_callback") |
| 673 | + |
| 674 | + if keras_history is not None: |
| 675 | + _, eval_lines, eval_results = parse_keras_history(keras_history) |
| 676 | + else: |
| 677 | + eval_lines = [] |
| 678 | + eval_results = dict() |
| 679 | + hyperparameters = extract_hyperparameters_from_keras(model) |
| 680 | + |
| 681 | + return cls( |
| 682 | + language=language, |
| 683 | + license=license, |
| 684 | + tags=tags, |
| 685 | + model_name=model_name, |
| 686 | + finetuned_from=finetuned_from, |
| 687 | + tasks=tasks, |
| 688 | + dataset_tags=dataset_tags, |
| 689 | + dataset=dataset, |
| 690 | + dataset_args=dataset_args, |
| 691 | + eval_results=eval_results, |
| 692 | + eval_lines=eval_lines, |
| 693 | + hyperparameters=hyperparameters, |
| 694 | + source="keras", |
| 695 | + ) |
| 696 | + |
| 697 | + |
| 698 | +def parse_keras_history(logs): |
| 699 | + """ |
| 700 | + Parse the `logs` of either a `tf.keras.History` object returned by `model.fit()` or an accumulated logs `dict` |
| 701 | + passed to the `PushToHubCallback`. Returns lines and logs compatible with those returned by `parse_log_history`. |
| 702 | + """ |
| 703 | + if hasattr(logs, "history"): |
| 704 | + # This looks like a `History` object |
| 705 | + logs.history["epoch"] = logs.epoch |
| 706 | + logs = logs.history |
| 707 | + else: |
| 708 | + # Training logs is a list of dicts, let's invert it to a dict of lists to match a History object |
| 709 | + logs = {log_key: [single_dict[log_key] for single_dict in logs] for log_key in logs[0]} |
| 710 | + |
| 711 | + lines = [] |
| 712 | + for i in range(len(logs["epoch"])): |
| 713 | + epoch_dict = {log_key: log_value_list[i] for log_key, log_value_list in logs.items()} |
| 714 | + values = dict() |
| 715 | + for k, v in epoch_dict.items(): |
| 716 | + if k.startswith("val_"): |
| 717 | + k = "validation_" + k[4:] |
| 718 | + elif k != "epoch": |
| 719 | + k = "train_" + k |
| 720 | + splits = k.split("_") |
| 721 | + name = " ".join([part.capitalize() for part in splits]) |
| 722 | + values[name] = v |
| 723 | + lines.append(values) |
| 724 | + |
| 725 | + eval_results = lines[-1] |
| 726 | + |
| 727 | + return logs, lines, eval_results |
| 728 | + |
607 | 729 |
|
608 | 730 | def parse_log_history(log_history):
|
609 | 731 | """
|
@@ -666,6 +788,19 @@ def parse_log_history(log_history):
|
666 | 788 | return train_log, lines, None
|
667 | 789 |
|
668 | 790 |
|
| 791 | +def extract_hyperparameters_from_keras(model): |
| 792 | + import tensorflow as tf |
| 793 | + |
| 794 | + hyperparameters = dict() |
| 795 | + if hasattr(model, "optimizer") and model.optimizer is not None: |
| 796 | + hyperparameters["optimizer"] = model.optimizer.get_config() |
| 797 | + else: |
| 798 | + hyperparameters["optimizer"] = None |
| 799 | + hyperparameters["training_precision"] = tf.keras.mixed_precision.global_policy().name |
| 800 | + |
| 801 | + return hyperparameters |
| 802 | + |
| 803 | + |
669 | 804 | def _maybe_round(v, decimals=4):
|
670 | 805 | if isinstance(v, float) and len(str(v).split(".")) > 1 and len(str(v).split(".")[1]) > decimals:
|
671 | 806 | return f"{v:.{decimals}f}"
|
|
0 commit comments