prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Support epoch level logging for QuantizedLogger
  Currently this only work on a per batch bases and not with tensorboard using `update_freq="epoch"`
  
  </issue>
  <code>
  [start of larq/callbacks.py]
  1 import tensorflow as tf
  2 import numpy as np
  3 
  4 
  5 class QuantizationLogger(tf.keras.callbacks.Callback):
  6     """Callback that adds quantization specific metrics.
  7 
  8     !!! note ""
  9         In order for metrics to be picked up by TensorBoard this callback needs to be
  10         applied before the TensorBoard callback and use the same update frequency.
  11 
  12     !!! example
  13         ```python
  14         callbacks = [
  15             QuantizationLogger(update_freq=100),
  16             tf.keras.callbacks.TensorBoard(update_freq=100),
  17         ]
  18         model.fit(X_train, Y_train, callbacks=callbacks)
  19         ```
  20 
  21     # Metrics
  22     - `changed_quantization_ration`: The ration of quantized weights in each layer that
  23         changed during the weight update.
  24 
  25     # Arguments
  26     update_freq: `'batch'` or integer. When using `'batch'`, computes the metrics after
  27         each batch. If using an integer the callback will compute the metrics every
  28         `update_freq` batches. Note that computing too frequently can slow down training.
  29     """
  30 
  31     def __init__(self, update_freq="batch"):
  32         self.previous_weights = {}
  33         self.update_freq = update_freq if update_freq != "batch" else 1
  34 
  35     def on_batch_end(self, batch, logs=None):
  36         should_log = batch > 0 and (batch + 1) % self.update_freq == 0
  37         should_store = (batch + 2) % self.update_freq == 0
  38 
  39         if should_log or should_store:
  40             ops = []
  41             op_names = []
  42             for layer in self.model.layers:
  43                 if hasattr(layer, "quantized_weights"):
  44                     for i, weight in enumerate(layer.quantized_weights):
  45                         ops.append(weight)
  46                         op_names.append(layer.name if i == 0 else f"{layer.name}_{i}")
  47 
  48             for key, value in zip(op_names, tf.keras.backend.batch_get_value(ops)):
  49                 if should_log:
  50                     logs[f"changed_quantization_ration/{key.replace(':', '_')}"] = 1 - (
  51                         np.count_nonzero(value == self.previous_weights[key])
  52                         / value.size
  53                     )
  54                 if should_store:
  55                     self.previous_weights[key] = value
  56 
  57             if should_log and not should_store:
  58                 # We don't need it in the next batch anymore
  59                 self.previous_weights = {}
  60
  [end of larq/callbacks.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/larq/callbacks.py b/larq/callbacks.py
  --- a/larq/callbacks.py
  +++ b/larq/callbacks.py
  @@ -11,10 +11,7 @@
   
       !!! example
           ```python
  -        callbacks = [
  -            QuantizationLogger(update_freq=100),
  -            tf.keras.callbacks.TensorBoard(update_freq=100),
  -        ]
  +        callbacks = [QuantizationLogger(), tf.keras.callbacks.TensorBoard()]
           model.fit(X_train, Y_train, callbacks=callbacks)
           ```
   
  @@ -23,19 +20,18 @@
           changed during the weight update.
   
       # Arguments
  -    update_freq: `'batch'` or integer. When using `'batch'`, computes the metrics after
  -        each batch. If using an integer the callback will compute the metrics every
  -        `update_freq` batches. Note that computing too frequently can slow down training.
  +    update_freq: `'batch'` or `'epoch'` or integer. When using `'batch'`, computes the
  +        metrics after each batch. The same applies for `'epoch'`. If using an integer
  +        the callback will compute the metrics every `update_freq` batches.
  +        Note that computing too frequently can slow down training.
       """
   
  -    def __init__(self, update_freq="batch"):
  -        self.previous_weights = {}
  +    def __init__(self, update_freq="epoch"):
  +        self.batch_previous_weights = {}
  +        self.epoch_previous_weights = {}
           self.update_freq = update_freq if update_freq != "batch" else 1
   
  -    def on_batch_end(self, batch, logs=None):
  -        should_log = batch > 0 and (batch + 1) % self.update_freq == 0
  -        should_store = (batch + 2) % self.update_freq == 0
  -
  +    def _maybe_log_and_store(self, storage, logs, should_log=True, should_store=True):
           if should_log or should_store:
               ops = []
               op_names = []
  @@ -46,14 +42,29 @@
                           op_names.append(layer.name if i == 0 else f"{layer.name}_{i}")
   
               for key, value in zip(op_names, tf.keras.backend.batch_get_value(ops)):
  +                value = value.astype(np.int8)
                   if should_log:
                       logs[f"changed_quantization_ration/{key.replace(':', '_')}"] = 1 - (
  -                        np.count_nonzero(value == self.previous_weights[key])
  -                        / value.size
  +                        np.count_nonzero(value == storage[key]) / value.size
                       )
                   if should_store:
  -                    self.previous_weights[key] = value
  +                    storage[key] = value
   
               if should_log and not should_store:
                   # We don't need it in the next batch anymore
  -                self.previous_weights = {}
  +                storage = {}
  +
  +    def on_batch_end(self, batch, logs=None):
  +        if self.update_freq != "epoch":
  +            self._maybe_log_and_store(
  +                self.batch_previous_weights,
  +                logs,
  +                should_log=batch > 0 and (batch + 1) % self.update_freq == 0,
  +                should_store=(batch + 2) % self.update_freq == 0,
  +            )
  +
  +    def on_train_begin(self, logs=None):
  +        self._maybe_log_and_store(self.epoch_previous_weights, logs, should_log=False)
  +
  +    def on_epoch_end(self, epoch, logs=None):
  +        self._maybe_log_and_store(self.epoch_previous_weights, logs)
  
verification_info: {"golden_diff": "diff --git a/larq/callbacks.py b/larq/callbacks.py\n--- a/larq/callbacks.py\n+++ b/larq/callbacks.py\n@@ -11,10 +11,7 @@\n \n     !!! example\n         ```python\n-        callbacks = [\n-            QuantizationLogger(update_freq=100),\n-            tf.keras.callbacks.TensorBoard(update_freq=100),\n-        ]\n+        callbacks = [QuantizationLogger(), tf.keras.callbacks.TensorBoard()]\n         model.fit(X_train, Y_train, callbacks=callbacks)\n         ```\n \n@@ -23,19 +20,18 @@\n         changed during the weight update.\n \n     # Arguments\n-    update_freq: `'batch'` or integer. When using `'batch'`, computes the metrics after\n-        each batch. If using an integer the callback will compute the metrics every\n-        `update_freq` batches. Note that computing too frequently can slow down training.\n+    update_freq: `'batch'` or `'epoch'` or integer. When using `'batch'`, computes the\n+        metrics after each batch. The same applies for `'epoch'`. If using an integer\n+        the callback will compute the metrics every `update_freq` batches.\n+        Note that computing too frequently can slow down training.\n     \"\"\"\n \n-    def __init__(self, update_freq=\"batch\"):\n-        self.previous_weights = {}\n+    def __init__(self, update_freq=\"epoch\"):\n+        self.batch_previous_weights = {}\n+        self.epoch_previous_weights = {}\n         self.update_freq = update_freq if update_freq != \"batch\" else 1\n \n-    def on_batch_end(self, batch, logs=None):\n-        should_log = batch > 0 and (batch + 1) % self.update_freq == 0\n-        should_store = (batch + 2) % self.update_freq == 0\n-\n+    def _maybe_log_and_store(self, storage, logs, should_log=True, should_store=True):\n         if should_log or should_store:\n             ops = []\n             op_names = []\n@@ -46,14 +42,29 @@\n                         op_names.append(layer.name if i == 0 else f\"{layer.name}_{i}\")\n \n             for key, value in zip(op_names, tf.keras.backend.batch_get_value(ops)):\n+                value = value.astype(np.int8)\n                 if should_log:\n                     logs[f\"changed_quantization_ration/{key.replace(':', '_')}\"] = 1 - (\n-                        np.count_nonzero(value == self.previous_weights[key])\n-                        / value.size\n+                        np.count_nonzero(value == storage[key]) / value.size\n                     )\n                 if should_store:\n-                    self.previous_weights[key] = value\n+                    storage[key] = value\n \n             if should_log and not should_store:\n                 # We don't need it in the next batch anymore\n-                self.previous_weights = {}\n+                storage = {}\n+\n+    def on_batch_end(self, batch, logs=None):\n+        if self.update_freq != \"epoch\":\n+            self._maybe_log_and_store(\n+                self.batch_previous_weights,\n+                logs,\n+                should_log=batch > 0 and (batch + 1) % self.update_freq == 0,\n+                should_store=(batch + 2) % self.update_freq == 0,\n+            )\n+\n+    def on_train_begin(self, logs=None):\n+        self._maybe_log_and_store(self.epoch_previous_weights, logs, should_log=False)\n+\n+    def on_epoch_end(self, epoch, logs=None):\n+        self._maybe_log_and_store(self.epoch_previous_weights, logs)\n", "issue": "Support epoch level logging for QuantizedLogger\nCurrently this only work on a per batch bases and not with tensorboard using `update_freq=\"epoch\"`\n", "before_files": [{"content": "import tensorflow as tf\nimport numpy as np\n\n\nclass QuantizationLogger(tf.keras.callbacks.Callback):\n    \"\"\"Callback that adds quantization specific metrics.\n\n    !!! note \"\"\n        In order for metrics to be picked up by TensorBoard this callback needs to be\n        applied before the TensorBoard callback and use the same update frequency.\n\n    !!! example\n        ```python\n        callbacks = [\n            QuantizationLogger(update_freq=100),\n            tf.keras.callbacks.TensorBoard(update_freq=100),\n        ]\n        model.fit(X_train, Y_train, callbacks=callbacks)\n        ```\n\n    # Metrics\n    - `changed_quantization_ration`: The ration of quantized weights in each layer that\n        changed during the weight update.\n\n    # Arguments\n    update_freq: `'batch'` or integer. When using `'batch'`, computes the metrics after\n        each batch. If using an integer the callback will compute the metrics every\n        `update_freq` batches. Note that computing too frequently can slow down training.\n    \"\"\"\n\n    def __init__(self, update_freq=\"batch\"):\n        self.previous_weights = {}\n        self.update_freq = update_freq if update_freq != \"batch\" else 1\n\n    def on_batch_end(self, batch, logs=None):\n        should_log = batch > 0 and (batch + 1) % self.update_freq == 0\n        should_store = (batch + 2) % self.update_freq == 0\n\n        if should_log or should_store:\n            ops = []\n            op_names = []\n            for layer in self.model.layers:\n                if hasattr(layer, \"quantized_weights\"):\n                    for i, weight in enumerate(layer.quantized_weights):\n                        ops.append(weight)\n                        op_names.append(layer.name if i == 0 else f\"{layer.name}_{i}\")\n\n            for key, value in zip(op_names, tf.keras.backend.batch_get_value(ops)):\n                if should_log:\n                    logs[f\"changed_quantization_ration/{key.replace(':', '_')}\"] = 1 - (\n                        np.count_nonzero(value == self.previous_weights[key])\n                        / value.size\n                    )\n                if should_store:\n                    self.previous_weights[key] = value\n\n            if should_log and not should_store:\n                # We don't need it in the next batch anymore\n                self.previous_weights = {}\n", "path": "larq/callbacks.py"}]}