prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Stats precision of FileTrainLogger
  Now, all the stats logged by a FileTrainLogger have the precision 2 after their decimal points. In some training scenarios, precision 2 is not enough for some stats. I suggest allowing users to decide precision for each stats or adding precision number to 4 or 5 uniformly.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `speechbrain/utils/train_logger.py`
  Content:
  ```
  1 """
  2 Loggers for experiment monitoring
  3 
  4 Authors
  5  * Peter Plantinga 2020
  6 """
  7 import logging
  8 from speechbrain.utils.edit_distance import wer_summary
  9 
  10 logger = logging.getLogger(__name__)
  11 
  12 
  13 class TrainLogger:
  14     """Abstract class defining an interface for training loggers."""
  15 
  16     def log_stats(
  17         self,
  18         stats_meta,
  19         train_stats=None,
  20         valid_stats=None,
  21         test_stats=None,
  22         verbose=False,
  23     ):
  24         """Log the stats for one epoch.
  25 
  26         Arguments
  27         ---------
  28         stats_meta : dict of str:scalar pairs
  29             Meta information about the stats (e.g. epoch, learning-rate, etc.)
  30         train_stats : dict of str:list pairs
  31             Each loss type is represented with a str : list pair including
  32             all the values for the training pass.
  33         valid_stats : dict of str:list pairs
  34             Each loss type is represented with a str : list pair including
  35             all the values for the validation pass.
  36         test_stats : dict of str:list pairs
  37             Each loss type is represented with a str : list pair including
  38             all the values for the test pass.
  39         verbose : bool
  40             Whether to also put logging information to the standard logger.
  41         """
  42         raise NotImplementedError
  43 
  44 
  45 class FileTrainLogger(TrainLogger):
  46     """Text logger of training information
  47 
  48     Arguments
  49     ---------
  50     save_file : str
  51         The file to use for logging train information.
  52     summary_fns : dict of str:function pairs
  53         Each summary function should take a list produced as output
  54         from a training/validation pass and summarize it to a single scalar.
  55     """
  56 
  57     def __init__(self, save_file, summary_fns=None):
  58         self.save_file = save_file
  59         self.summary_fns = summary_fns or {}
  60 
  61     def _item_to_string(self, key, value, dataset=None):
  62         """Convert one item to string, handling floats"""
  63         if isinstance(value, float) and 0.01 < value < 100.0:
  64             value = f"{value:.2f}"
  65         elif isinstance(value, float):
  66             value = f"{value:.2e}"
  67         if dataset is not None:
  68             key = f"{dataset} {key}"
  69         return f"{key}: {value}"
  70 
  71     def _stats_to_string(self, stats, dataset=None):
  72         """Convert all stats to a single string summary"""
  73         return ", ".join(
  74             [self._item_to_string(k, v, dataset) for k, v in stats.items()]
  75         )
  76 
  77     def log_stats(
  78         self,
  79         stats_meta,
  80         train_stats=None,
  81         valid_stats=None,
  82         test_stats=None,
  83         verbose=True,
  84     ):
  85         """See TrainLogger.log_stats()"""
  86         string_summary = self._stats_to_string(stats_meta)
  87         for dataset, stats in [
  88             ("train", train_stats),
  89             ("valid", valid_stats),
  90             ("test", test_stats),
  91         ]:
  92             if stats is None:
  93                 continue
  94             summary = {}
  95             for stat, value_list in stats.items():
  96                 if stat in self.summary_fns:
  97                     summary[stat] = self.summary_fns[stat](value_list)
  98                 else:
  99                     summary[stat] = summarize_average(value_list)
  100             string_summary += " - " + self._stats_to_string(summary, dataset)
  101 
  102         with open(self.save_file, "a") as fout:
  103             print(string_summary, file=fout)
  104         if verbose:
  105             logger.info(string_summary)
  106 
  107 
  108 class TensorboardLogger(TrainLogger):
  109     """Logs training information in the format required by Tensorboard.
  110 
  111     Arguments
  112     ---------
  113     save_dir : str
  114         A directory for storing all the relevant logs
  115 
  116     Raises
  117     ------
  118     ImportError if Tensorboard is not installed.
  119     """
  120 
  121     def __init__(self, save_dir):
  122         self.save_dir = save_dir
  123 
  124         # Raises ImportError if TensorBoard is not installed
  125         from torch.utils.tensorboard import SummaryWriter
  126 
  127         self.writer = SummaryWriter(self.save_dir)
  128         self.global_step = {"train": {}, "valid": {}, "meta": 0}
  129 
  130     def log_stats(
  131         self,
  132         stats_meta,
  133         train_stats=None,
  134         valid_stats=None,
  135         test_stats=None,
  136         verbose=False,
  137     ):
  138         """See TrainLogger.log_stats()"""
  139         self.global_step["meta"] += 1
  140         for name, value in stats_meta.items():
  141             self.writer.add_scalar(name, value, self.global_step["meta"])
  142 
  143         for dataset, stats in [
  144             ("train", train_stats),
  145             ("valid", valid_stats),
  146             ("test", test_stats),
  147         ]:
  148             if stats is None:
  149                 continue
  150             for stat, value_list in stats.items():
  151                 if stat not in self.global_step[dataset]:
  152                     self.global_step[dataset][stat] = 0
  153                 tag = f"{stat}/{dataset}"
  154                 for value in value_list:
  155                     new_global_step = self.global_step[dataset][stat] + 1
  156                     self.writer.add_scalar(tag, value, new_global_step)
  157                     self.global_step[dataset][stat] = new_global_step
  158 
  159 
  160 def summarize_average(stat_list):
  161     return float(sum(stat_list) / len(stat_list))
  162 
  163 
  164 def summarize_error_rate(stat_list):
  165     summary = wer_summary(stat_list)
  166     return summary["WER"]
  167
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/speechbrain/utils/train_logger.py b/speechbrain/utils/train_logger.py
  --- a/speechbrain/utils/train_logger.py
  +++ b/speechbrain/utils/train_logger.py
  @@ -49,21 +49,24 @@
       ---------
       save_file : str
           The file to use for logging train information.
  +    precision : int
  +        Number of decimal places to display. Default 2, example: 1.35e-5
       summary_fns : dict of str:function pairs
           Each summary function should take a list produced as output
           from a training/validation pass and summarize it to a single scalar.
       """
   
  -    def __init__(self, save_file, summary_fns=None):
  +    def __init__(self, save_file, precision=2, summary_fns=None):
           self.save_file = save_file
  +        self.precision = precision
           self.summary_fns = summary_fns or {}
   
       def _item_to_string(self, key, value, dataset=None):
           """Convert one item to string, handling floats"""
  -        if isinstance(value, float) and 0.01 < value < 100.0:
  -            value = f"{value:.2f}"
  +        if isinstance(value, float) and 1.0 < value < 100.0:
  +            value = f"{value:.{self.precision}f}"
           elif isinstance(value, float):
  -            value = f"{value:.2e}"
  +            value = f"{value:.{self.precision}e}"
           if dataset is not None:
               key = f"{dataset} {key}"
           return f"{key}: {value}"
  
verification_info: {"golden_diff": "diff --git a/speechbrain/utils/train_logger.py b/speechbrain/utils/train_logger.py\n--- a/speechbrain/utils/train_logger.py\n+++ b/speechbrain/utils/train_logger.py\n@@ -49,21 +49,24 @@\n     ---------\n     save_file : str\n         The file to use for logging train information.\n+    precision : int\n+        Number of decimal places to display. Default 2, example: 1.35e-5\n     summary_fns : dict of str:function pairs\n         Each summary function should take a list produced as output\n         from a training/validation pass and summarize it to a single scalar.\n     \"\"\"\n \n-    def __init__(self, save_file, summary_fns=None):\n+    def __init__(self, save_file, precision=2, summary_fns=None):\n         self.save_file = save_file\n+        self.precision = precision\n         self.summary_fns = summary_fns or {}\n \n     def _item_to_string(self, key, value, dataset=None):\n         \"\"\"Convert one item to string, handling floats\"\"\"\n-        if isinstance(value, float) and 0.01 < value < 100.0:\n-            value = f\"{value:.2f}\"\n+        if isinstance(value, float) and 1.0 < value < 100.0:\n+            value = f\"{value:.{self.precision}f}\"\n         elif isinstance(value, float):\n-            value = f\"{value:.2e}\"\n+            value = f\"{value:.{self.precision}e}\"\n         if dataset is not None:\n             key = f\"{dataset} {key}\"\n         return f\"{key}: {value}\"\n", "issue": "Stats precision of FileTrainLogger\nNow, all the stats logged by a FileTrainLogger have the precision 2 after their decimal points. In some training scenarios, precision 2 is not enough for some stats. I suggest allowing users to decide precision for each stats or adding precision number to 4 or 5 uniformly.\n", "before_files": [{"content": "\"\"\"\nLoggers for experiment monitoring\n\nAuthors\n * Peter Plantinga 2020\n\"\"\"\nimport logging\nfrom speechbrain.utils.edit_distance import wer_summary\n\nlogger = logging.getLogger(__name__)\n\n\nclass TrainLogger:\n    \"\"\"Abstract class defining an interface for training loggers.\"\"\"\n\n    def log_stats(\n        self,\n        stats_meta,\n        train_stats=None,\n        valid_stats=None,\n        test_stats=None,\n        verbose=False,\n    ):\n        \"\"\"Log the stats for one epoch.\n\n        Arguments\n        ---------\n        stats_meta : dict of str:scalar pairs\n            Meta information about the stats (e.g. epoch, learning-rate, etc.)\n        train_stats : dict of str:list pairs\n            Each loss type is represented with a str : list pair including\n            all the values for the training pass.\n        valid_stats : dict of str:list pairs\n            Each loss type is represented with a str : list pair including\n            all the values for the validation pass.\n        test_stats : dict of str:list pairs\n            Each loss type is represented with a str : list pair including\n            all the values for the test pass.\n        verbose : bool\n            Whether to also put logging information to the standard logger.\n        \"\"\"\n        raise NotImplementedError\n\n\nclass FileTrainLogger(TrainLogger):\n    \"\"\"Text logger of training information\n\n    Arguments\n    ---------\n    save_file : str\n        The file to use for logging train information.\n    summary_fns : dict of str:function pairs\n        Each summary function should take a list produced as output\n        from a training/validation pass and summarize it to a single scalar.\n    \"\"\"\n\n    def __init__(self, save_file, summary_fns=None):\n        self.save_file = save_file\n        self.summary_fns = summary_fns or {}\n\n    def _item_to_string(self, key, value, dataset=None):\n        \"\"\"Convert one item to string, handling floats\"\"\"\n        if isinstance(value, float) and 0.01 < value < 100.0:\n            value = f\"{value:.2f}\"\n        elif isinstance(value, float):\n            value = f\"{value:.2e}\"\n        if dataset is not None:\n            key = f\"{dataset} {key}\"\n        return f\"{key}: {value}\"\n\n    def _stats_to_string(self, stats, dataset=None):\n        \"\"\"Convert all stats to a single string summary\"\"\"\n        return \", \".join(\n            [self._item_to_string(k, v, dataset) for k, v in stats.items()]\n        )\n\n    def log_stats(\n        self,\n        stats_meta,\n        train_stats=None,\n        valid_stats=None,\n        test_stats=None,\n        verbose=True,\n    ):\n        \"\"\"See TrainLogger.log_stats()\"\"\"\n        string_summary = self._stats_to_string(stats_meta)\n        for dataset, stats in [\n            (\"train\", train_stats),\n            (\"valid\", valid_stats),\n            (\"test\", test_stats),\n        ]:\n            if stats is None:\n                continue\n            summary = {}\n            for stat, value_list in stats.items():\n                if stat in self.summary_fns:\n                    summary[stat] = self.summary_fns[stat](value_list)\n                else:\n                    summary[stat] = summarize_average(value_list)\n            string_summary += \" - \" + self._stats_to_string(summary, dataset)\n\n        with open(self.save_file, \"a\") as fout:\n            print(string_summary, file=fout)\n        if verbose:\n            logger.info(string_summary)\n\n\nclass TensorboardLogger(TrainLogger):\n    \"\"\"Logs training information in the format required by Tensorboard.\n\n    Arguments\n    ---------\n    save_dir : str\n        A directory for storing all the relevant logs\n\n    Raises\n    ------\n    ImportError if Tensorboard is not installed.\n    \"\"\"\n\n    def __init__(self, save_dir):\n        self.save_dir = save_dir\n\n        # Raises ImportError if TensorBoard is not installed\n        from torch.utils.tensorboard import SummaryWriter\n\n        self.writer = SummaryWriter(self.save_dir)\n        self.global_step = {\"train\": {}, \"valid\": {}, \"meta\": 0}\n\n    def log_stats(\n        self,\n        stats_meta,\n        train_stats=None,\n        valid_stats=None,\n        test_stats=None,\n        verbose=False,\n    ):\n        \"\"\"See TrainLogger.log_stats()\"\"\"\n        self.global_step[\"meta\"] += 1\n        for name, value in stats_meta.items():\n            self.writer.add_scalar(name, value, self.global_step[\"meta\"])\n\n        for dataset, stats in [\n            (\"train\", train_stats),\n            (\"valid\", valid_stats),\n            (\"test\", test_stats),\n        ]:\n            if stats is None:\n                continue\n            for stat, value_list in stats.items():\n                if stat not in self.global_step[dataset]:\n                    self.global_step[dataset][stat] = 0\n                tag = f\"{stat}/{dataset}\"\n                for value in value_list:\n                    new_global_step = self.global_step[dataset][stat] + 1\n                    self.writer.add_scalar(tag, value, new_global_step)\n                    self.global_step[dataset][stat] = new_global_step\n\n\ndef summarize_average(stat_list):\n    return float(sum(stat_list) / len(stat_list))\n\n\ndef summarize_error_rate(stat_list):\n    summary = wer_summary(stat_list)\n    return summary[\"WER\"]\n", "path": "speechbrain/utils/train_logger.py"}], "after_files": [{"content": "\"\"\"\nLoggers for experiment monitoring\n\nAuthors\n * Peter Plantinga 2020\n\"\"\"\nimport logging\nfrom speechbrain.utils.edit_distance import wer_summary\n\nlogger = logging.getLogger(__name__)\n\n\nclass TrainLogger:\n    \"\"\"Abstract class defining an interface for training loggers.\"\"\"\n\n    def log_stats(\n        self,\n        stats_meta,\n        train_stats=None,\n        valid_stats=None,\n        test_stats=None,\n        verbose=False,\n    ):\n        \"\"\"Log the stats for one epoch.\n\n        Arguments\n        ---------\n        stats_meta : dict of str:scalar pairs\n            Meta information about the stats (e.g. epoch, learning-rate, etc.)\n        train_stats : dict of str:list pairs\n            Each loss type is represented with a str : list pair including\n            all the values for the training pass.\n        valid_stats : dict of str:list pairs\n            Each loss type is represented with a str : list pair including\n            all the values for the validation pass.\n        test_stats : dict of str:list pairs\n            Each loss type is represented with a str : list pair including\n            all the values for the test pass.\n        verbose : bool\n            Whether to also put logging information to the standard logger.\n        \"\"\"\n        raise NotImplementedError\n\n\nclass FileTrainLogger(TrainLogger):\n    \"\"\"Text logger of training information\n\n    Arguments\n    ---------\n    save_file : str\n        The file to use for logging train information.\n    precision : int\n        Number of decimal places to display. Default 2, example: 1.35e-5\n    summary_fns : dict of str:function pairs\n        Each summary function should take a list produced as output\n        from a training/validation pass and summarize it to a single scalar.\n    \"\"\"\n\n    def __init__(self, save_file, precision=2, summary_fns=None):\n        self.save_file = save_file\n        self.precision = precision\n        self.summary_fns = summary_fns or {}\n\n    def _item_to_string(self, key, value, dataset=None):\n        \"\"\"Convert one item to string, handling floats\"\"\"\n        if isinstance(value, float) and 1.0 < value < 100.0:\n            value = f\"{value:.{self.precision}f}\"\n        elif isinstance(value, float):\n            value = f\"{value:.{self.precision}e}\"\n        if dataset is not None:\n            key = f\"{dataset} {key}\"\n        return f\"{key}: {value}\"\n\n    def _stats_to_string(self, stats, dataset=None):\n        \"\"\"Convert all stats to a single string summary\"\"\"\n        return \", \".join(\n            [self._item_to_string(k, v, dataset) for k, v in stats.items()]\n        )\n\n    def log_stats(\n        self,\n        stats_meta,\n        train_stats=None,\n        valid_stats=None,\n        test_stats=None,\n        verbose=True,\n    ):\n        \"\"\"See TrainLogger.log_stats()\"\"\"\n        string_summary = self._stats_to_string(stats_meta)\n        for dataset, stats in [\n            (\"train\", train_stats),\n            (\"valid\", valid_stats),\n            (\"test\", test_stats),\n        ]:\n            if stats is None:\n                continue\n            summary = {}\n            for stat, value_list in stats.items():\n                if stat in self.summary_fns:\n                    summary[stat] = self.summary_fns[stat](value_list)\n                else:\n                    summary[stat] = summarize_average(value_list)\n            string_summary += \" - \" + self._stats_to_string(summary, dataset)\n\n        with open(self.save_file, \"a\") as fout:\n            print(string_summary, file=fout)\n        if verbose:\n            logger.info(string_summary)\n\n\nclass TensorboardLogger(TrainLogger):\n    \"\"\"Logs training information in the format required by Tensorboard.\n\n    Arguments\n    ---------\n    save_dir : str\n        A directory for storing all the relevant logs\n\n    Raises\n    ------\n    ImportError if Tensorboard is not installed.\n    \"\"\"\n\n    def __init__(self, save_dir):\n        self.save_dir = save_dir\n\n        # Raises ImportError if TensorBoard is not installed\n        from torch.utils.tensorboard import SummaryWriter\n\n        self.writer = SummaryWriter(self.save_dir)\n        self.global_step = {\"train\": {}, \"valid\": {}, \"meta\": 0}\n\n    def log_stats(\n        self,\n        stats_meta,\n        train_stats=None,\n        valid_stats=None,\n        test_stats=None,\n        verbose=False,\n    ):\n        \"\"\"See TrainLogger.log_stats()\"\"\"\n        self.global_step[\"meta\"] += 1\n        for name, value in stats_meta.items():\n            self.writer.add_scalar(name, value, self.global_step[\"meta\"])\n\n        for dataset, stats in [\n            (\"train\", train_stats),\n            (\"valid\", valid_stats),\n            (\"test\", test_stats),\n        ]:\n            if stats is None:\n                continue\n            for stat, value_list in stats.items():\n                if stat not in self.global_step[dataset]:\n                    self.global_step[dataset][stat] = 0\n                tag = f\"{stat}/{dataset}\"\n                for value in value_list:\n                    new_global_step = self.global_step[dataset][stat] + 1\n                    self.writer.add_scalar(tag, value, new_global_step)\n                    self.global_step[dataset][stat] = new_global_step\n\n\ndef summarize_average(stat_list):\n    return float(sum(stat_list) / len(stat_list))\n\n\ndef summarize_error_rate(stat_list):\n    summary = wer_summary(stat_list)\n    return summary[\"WER\"]\n", "path": "speechbrain/utils/train_logger.py"}]}