278 строки
9.5 KiB
Python
278 строки
9.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
# File: summary.py
|
|
|
|
|
|
import re
|
|
from contextlib import contextmanager
|
|
import six
|
|
from six.moves import range
|
|
from tensorflow.python.training import moving_averages
|
|
|
|
from ..compat import tfv1 as tf
|
|
from ..utils import logger
|
|
from ..utils.argtools import graph_memoized
|
|
from ..utils.naming import MOVING_SUMMARY_OPS_KEY
|
|
from .scope_utils import cached_name_scope
|
|
from .symbolic_functions import rms
|
|
from .tower import get_current_tower_context
|
|
|
|
__all__ = ['add_tensor_summary', 'add_param_summary',
|
|
'add_activation_summary', 'add_moving_summary',
|
|
]
|
|
|
|
|
|
# some scope stuff to use internally...
|
|
@graph_memoized
|
|
def _get_cached_vs(name):
|
|
with tf.variable_scope(name) as scope:
|
|
return scope
|
|
|
|
|
|
@contextmanager
|
|
def _enter_vs_reuse_ns(name):
|
|
vs = _get_cached_vs(name)
|
|
# XXX Not good to enter the cached vs directly, because this will clean-up custom getter
|
|
# with tf.variable_scope(name, reuse=tf.AUTO_REUSE): # available in 1.4 only
|
|
with tf.variable_scope(vs):
|
|
with tf.name_scope(vs.original_name_scope):
|
|
yield vs
|
|
|
|
|
|
def create_scalar_summary(name, v):
|
|
"""
|
|
Args:
|
|
name (str):
|
|
v (float): scalar value
|
|
Returns:
|
|
tf.Summary: a tf.Summary object with name and simple scalar value v.
|
|
"""
|
|
assert isinstance(name, six.string_types), type(name)
|
|
v = float(v)
|
|
s = tf.Summary()
|
|
s.value.add(tag=name, simple_value=v)
|
|
return s
|
|
|
|
|
|
def create_image_summary(name, val):
|
|
"""
|
|
Args:
|
|
name(str):
|
|
val(np.ndarray): 4D tensor of NHWC. assume RGB if C==3.
|
|
Can be either float or uint8. Range has to be [0,255].
|
|
|
|
Returns:
|
|
tf.Summary:
|
|
"""
|
|
assert isinstance(name, six.string_types), type(name)
|
|
n, h, w, c = val.shape
|
|
val = val.astype('uint8')
|
|
s = tf.Summary()
|
|
imparams = [cv2.IMWRITE_PNG_COMPRESSION, 9]
|
|
for k in range(n):
|
|
arr = val[k]
|
|
# CV2 will only write correctly in BGR chanel order
|
|
if c == 3:
|
|
arr = cv2.cvtColor(arr, cv2.COLOR_RGB2BGR)
|
|
elif c == 4:
|
|
arr = cv2.cvtColor(arr, cv2.COLOR_RGBA2BGRA)
|
|
tag = name if n == 1 else '{}/{}'.format(name, k)
|
|
retval, img_str = cv2.imencode('.png', arr, imparams)
|
|
if not retval:
|
|
# Encoding has failed.
|
|
continue
|
|
img_str = img_str.tostring()
|
|
|
|
img = tf.Summary.Image()
|
|
img.height = h
|
|
img.width = w
|
|
# 1 - grayscale 3 - RGB 4 - RGBA
|
|
img.colorspace = c
|
|
img.encoded_image_string = img_str
|
|
s.value.add(tag=tag, image=img)
|
|
return s
|
|
|
|
|
|
def add_tensor_summary(x, types, name=None, collections=None,
|
|
main_tower_only=True):
|
|
"""
|
|
Summarize a tensor by different methods.
|
|
|
|
Args:
|
|
x (tf.Tensor): a tensor to summarize
|
|
types (list[str]): summary types, can be scalar/histogram/sparsity/mean/rms
|
|
name (str): summary name. Defaults to be the op name.
|
|
collections (list[str]): collections of the summary ops.
|
|
main_tower_only (bool): Only run under main training tower. If
|
|
set to True, calling this function under other TowerContext
|
|
has no effect.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
with tf.name_scope('mysummaries'): # to not mess up tensorboard
|
|
add_tensor_summary(
|
|
tensor, ['histogram', 'rms', 'sparsity'], name='mytensor')
|
|
"""
|
|
types = set(types)
|
|
if name is None:
|
|
name = x.op.name
|
|
ctx = get_current_tower_context()
|
|
if main_tower_only and ctx is not None and not ctx.is_main_training_tower:
|
|
return
|
|
|
|
SUMMARY_TYPES_DIC = {
|
|
'scalar': lambda: tf.summary.scalar(name + '-summary', x, collections=collections),
|
|
'histogram': lambda: tf.summary.histogram(name + '-histogram', x, collections=collections),
|
|
'sparsity': lambda: tf.summary.scalar(
|
|
name + '-sparsity', tf.nn.zero_fraction(x),
|
|
collections=collections),
|
|
'mean': lambda: tf.summary.scalar(
|
|
name + '-mean', tf.reduce_mean(x),
|
|
collections=collections),
|
|
'rms': lambda: tf.summary.scalar(
|
|
name + '-rms', rms(x), collections=collections)
|
|
}
|
|
for typ in types:
|
|
SUMMARY_TYPES_DIC[typ]()
|
|
|
|
|
|
def add_activation_summary(x, types=None, name=None, collections=None):
|
|
"""
|
|
Call :func:`add_tensor_summary` under a reused 'activation-summary' name scope.
|
|
This function is a no-op if not calling from main training tower.
|
|
|
|
Args:
|
|
x (tf.Tensor): the tensor to summary.
|
|
types (list[str]): summary types, defaults to ``['sparsity', 'rms', 'histogram']``.
|
|
name (str): if is None, use x.name.
|
|
collections (list[str]): collections of the summary ops.
|
|
"""
|
|
ndim = x.get_shape().ndims
|
|
if ndim < 2:
|
|
logger.warn("Cannot summarize scalar activation {}".format(x.name))
|
|
return
|
|
if types is None:
|
|
types = ['sparsity', 'rms', 'histogram']
|
|
with cached_name_scope('activation-summary'):
|
|
add_tensor_summary(x, types, name=name, collections=collections)
|
|
|
|
|
|
def add_param_summary(*summary_lists, **kwargs):
|
|
"""
|
|
Add summary ops for all trainable variables matching the regex, under a
|
|
reused 'param-summary' name scope.
|
|
This function is a no-op if not calling from main training tower.
|
|
|
|
Args:
|
|
summary_lists (list): each is (regex, [list of summary type]).
|
|
Summary type is defined in :func:`add_tensor_summary`.
|
|
collections (list[str]): collections of the summary ops.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
add_param_summary(
|
|
('.*/W', ['histogram', 'rms']),
|
|
('.*/gamma', ['scalar']),
|
|
)
|
|
"""
|
|
collections = kwargs.pop('collections', None)
|
|
assert len(kwargs) == 0, "Unknown kwargs: " + str(kwargs)
|
|
ctx = get_current_tower_context()
|
|
if ctx is not None and not ctx.is_main_training_tower:
|
|
return
|
|
|
|
params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)
|
|
with cached_name_scope('param-summary'):
|
|
for p in params:
|
|
name = p.op.name
|
|
for rgx, actions in summary_lists:
|
|
if not rgx.endswith('$'):
|
|
rgx = rgx + '$'
|
|
if re.match(rgx, name):
|
|
add_tensor_summary(p, actions, name=name, collections=collections)
|
|
|
|
|
|
def add_moving_summary(*args, **kwargs):
|
|
"""
|
|
Summarize the moving average for scalar tensors.
|
|
This function is a no-op if not calling from main training tower.
|
|
|
|
Args:
|
|
args: scalar tensors to summarize
|
|
decay (float): the decay rate. Defaults to 0.95.
|
|
collection (str or None): the name of the collection to add EMA-maintaining ops.
|
|
The default will work together with the default
|
|
:class:`MovingAverageSummary` callback.
|
|
summary_collections ([str]): the names of collections to add the
|
|
summary op. Default is TF's default (`tf.GraphKeys.SUMMARIES`).
|
|
|
|
Returns:
|
|
[tf.Tensor]: list of tensors returned by assign_moving_average,
|
|
which can be used to maintain the EMA.
|
|
"""
|
|
decay = kwargs.pop('decay', 0.95)
|
|
coll = kwargs.pop('collection', MOVING_SUMMARY_OPS_KEY)
|
|
summ_coll = kwargs.pop('summary_collections', None)
|
|
assert len(kwargs) == 0, "Unknown arguments: " + str(kwargs)
|
|
|
|
ctx = get_current_tower_context()
|
|
# allow ctx to be none
|
|
if ctx is not None and not ctx.is_main_training_tower:
|
|
return []
|
|
|
|
graph = tf.get_default_graph()
|
|
try:
|
|
control_flow_ctx = graph._get_control_flow_context()
|
|
# XLA does not support summaries anyway
|
|
# However, this function will generate unnecessary dependency edges,
|
|
# which makes the tower function harder to compile under XLA, so we skip it
|
|
if control_flow_ctx is not None and control_flow_ctx.IsXLAContext():
|
|
return
|
|
except Exception:
|
|
pass
|
|
|
|
if tf.get_variable_scope().reuse is True:
|
|
logger.warn("add_moving_summary() called under reuse=True scope, ignored.")
|
|
return []
|
|
|
|
for x in args:
|
|
assert isinstance(x, (tf.Tensor, tf.Variable)), x
|
|
assert x.get_shape().ndims == 0, \
|
|
"add_moving_summary() only accepts scalar tensor! Got one with {}".format(x.get_shape())
|
|
|
|
ema_ops = []
|
|
for c in args:
|
|
name = re.sub('tower[0-9]+/', '', c.op.name)
|
|
with tf.name_scope(None):
|
|
if not c.dtype.is_floating:
|
|
c = tf.cast(c, tf.float32)
|
|
# assign_moving_average creates variables with op names, therefore clear ns first.
|
|
with _enter_vs_reuse_ns('EMA') as vs:
|
|
ema_var = tf.get_variable(name, shape=c.shape, dtype=c.dtype,
|
|
initializer=tf.constant_initializer(),
|
|
trainable=False)
|
|
ns = vs.original_name_scope
|
|
with tf.name_scope(ns): # reuse VS&NS so that EMA_1 won't appear
|
|
ema_op = moving_averages.assign_moving_average(
|
|
ema_var, c, decay,
|
|
zero_debias=True, name=name + '_EMA_apply')
|
|
ema_ops.append(ema_op)
|
|
with tf.name_scope(None):
|
|
tf.summary.scalar(
|
|
name + '-summary', ema_op,
|
|
collections=summ_coll) # write the EMA value as a summary
|
|
if coll is not None:
|
|
for op in ema_ops:
|
|
tf.add_to_collection(coll, op)
|
|
return ema_ops
|
|
|
|
|
|
try:
|
|
import cv2
|
|
except ImportError:
|
|
from ..utils.develop import create_dummy_func
|
|
create_image_summary = create_dummy_func('create_image_summary', 'cv2') # noqa
|