Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tensorboard/plugins/histogram/summary_v2.py: 13%

82 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-03 07:57 +0000

1# Copyright 2018 The TensorFlow Authors. All Rights Reserved. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14# ============================================================================== 

15"""Histogram summaries and TensorFlow operations to create them, V2 versions. 

16 

17A histogram summary stores a list of buckets. Each bucket is encoded as a triple 

18`[left_edge, right_edge, count]`. Thus, a full histogram is encoded as a tensor 

19of dimension `[k, 3]`, where the first `k - 1` buckets are closed-open and the 

20last bucket is closed-closed. 

21 

22In general, the shape of the output histogram is always constant (`[k, 3]`). 

23In the case of empty data, the output will be an all-zero histogram of shape 

24`[k, 3]`, where all edges and counts are zeros. If there is data but all points 

25have the same value, then all buckets' left and right edges are the same and only 

26the last bucket has nonzero count. 

27""" 

28 

29import numpy as np 

30 

31from tensorboard.compat import tf2 as tf 

32from tensorboard.compat.proto import summary_pb2 

33from tensorboard.plugins.histogram import metadata 

34from tensorboard.util import lazy_tensor_creator 

35from tensorboard.util import tensor_util 

36 

37 

38DEFAULT_BUCKET_COUNT = 30 

39 

40 

41def histogram_pb(tag, data, buckets=None, description=None): 

42 """Create a histogram summary protobuf. 

43 

44 Arguments: 

45 tag: String tag for the summary. 

46 data: A `np.array` or array-like form of any shape. Must have type 

47 castable to `float`. 

48 buckets: Optional positive `int`. The output shape will always be 

49 [buckets, 3]. If there is no data, then an all-zero array of shape 

50 [buckets, 3] will be returned. If there is data but all points have 

51 the same value, then all buckets' left and right endpoints are the 

52 same and only the last bucket has nonzero count. Defaults to 30 if 

53 not specified. 

54 description: Optional long-form description for this summary, as a 

55 `str`. Markdown is supported. Defaults to empty. 

56 

57 Returns: 

58 A `summary_pb2.Summary` protobuf object. 

59 """ 

60 bucket_count = DEFAULT_BUCKET_COUNT if buckets is None else buckets 

61 data = np.array(data).flatten().astype(float) 

62 if bucket_count == 0 or data.size == 0: 

63 histogram_buckets = np.zeros((bucket_count, 3)) 

64 else: 

65 min_ = np.min(data) 

66 max_ = np.max(data) 

67 range_ = max_ - min_ 

68 if range_ == 0: 

69 left_edges = right_edges = np.array([min_] * bucket_count) 

70 bucket_counts = np.array([0] * (bucket_count - 1) + [data.size]) 

71 histogram_buckets = np.array( 

72 [left_edges, right_edges, bucket_counts] 

73 ).transpose() 

74 else: 

75 bucket_width = range_ / bucket_count 

76 offsets = data - min_ 

77 bucket_indices = np.floor(offsets / bucket_width).astype(int) 

78 clamped_indices = np.minimum(bucket_indices, bucket_count - 1) 

79 one_hots = np.array([clamped_indices]).transpose() == np.arange( 

80 0, bucket_count 

81 ) # broadcast 

82 assert one_hots.shape == (data.size, bucket_count), ( 

83 one_hots.shape, 

84 (data.size, bucket_count), 

85 ) 

86 bucket_counts = np.sum(one_hots, axis=0) 

87 edges = np.linspace(min_, max_, bucket_count + 1) 

88 left_edges = edges[:-1] 

89 right_edges = edges[1:] 

90 histogram_buckets = np.array( 

91 [left_edges, right_edges, bucket_counts] 

92 ).transpose() 

93 tensor = tensor_util.make_tensor_proto(histogram_buckets, dtype=np.float64) 

94 

95 summary_metadata = metadata.create_summary_metadata( 

96 display_name=None, description=description 

97 ) 

98 summary = summary_pb2.Summary() 

99 summary.value.add(tag=tag, metadata=summary_metadata, tensor=tensor) 

100 return summary 

101 

102 

103# This is the TPU compatible V3 histogram implementation as of 2021-12-01. 

104def histogram(name, data, step=None, buckets=None, description=None): 

105 """Write a histogram summary. 

106 

107 See also `tf.summary.scalar`, `tf.summary.SummaryWriter`. 

108 

109 Writes a histogram to the current default summary writer, for later analysis 

110 in TensorBoard's 'Histograms' and 'Distributions' dashboards (data written 

111 using this API will appear in both places). Like `tf.summary.scalar` points, 

112 each histogram is associated with a `step` and a `name`. All the histograms 

113 with the same `name` constitute a time series of histograms. 

114 

115 The histogram is calculated over all the elements of the given `Tensor` 

116 without regard to its shape or rank. 

117 

118 This example writes 2 histograms: 

119 

120 ```python 

121 w = tf.summary.create_file_writer('test/logs') 

122 with w.as_default(): 

123 tf.summary.histogram("activations", tf.random.uniform([100, 50]), step=0) 

124 tf.summary.histogram("initial_weights", tf.random.normal([1000]), step=0) 

125 ``` 

126 

127 A common use case is to examine the changing activation patterns (or lack 

128 thereof) at specific layers in a neural network, over time. 

129 

130 ```python 

131 w = tf.summary.create_file_writer('test/logs') 

132 with w.as_default(): 

133 for step in range(100): 

134 # Generate fake "activations". 

135 activations = [ 

136 tf.random.normal([1000], mean=step, stddev=1), 

137 tf.random.normal([1000], mean=step, stddev=10), 

138 tf.random.normal([1000], mean=step, stddev=100), 

139 ] 

140 

141 tf.summary.histogram("layer1/activate", activations[0], step=step) 

142 tf.summary.histogram("layer2/activate", activations[1], step=step) 

143 tf.summary.histogram("layer3/activate", activations[2], step=step) 

144 ``` 

145 

146 Arguments: 

147 name: A name for this summary. The summary tag used for TensorBoard will 

148 be this name prefixed by any active name scopes. 

149 data: A `Tensor` of any shape. The histogram is computed over its elements, 

150 which must be castable to `float64`. 

151 step: Explicit `int64`-castable monotonic step value for this summary. If 

152 omitted, this defaults to `tf.summary.experimental.get_step()`, which must 

153 not be None. 

154 buckets: Optional positive `int`. The output will have this 

155 many buckets, except in two edge cases. If there is no data, then 

156 there are no buckets. If there is data but all points have the 

157 same value, then all buckets' left and right endpoints are the same 

158 and only the last bucket has nonzero count. Defaults to 30 if not 

159 specified. 

160 description: Optional long-form description for this summary, as a 

161 constant `str`. Markdown is supported. Defaults to empty. 

162 

163 Returns: 

164 True on success, or false if no summary was emitted because no default 

165 summary writer was available. 

166 

167 Raises: 

168 ValueError: if a default writer exists, but no step was provided and 

169 `tf.summary.experimental.get_step()` is None. 

170 """ 

171 # Avoid building unused gradient graphs for conds below. This works around 

172 # an error building second-order gradient graphs when XlaDynamicUpdateSlice 

173 # is used, and will generally speed up graph building slightly. 

174 data = tf.stop_gradient(data) 

175 summary_metadata = metadata.create_summary_metadata( 

176 display_name=None, description=description 

177 ) 

178 # TODO(https://github.com/tensorflow/tensorboard/issues/2109): remove fallback 

179 summary_scope = ( 

180 getattr(tf.summary.experimental, "summary_scope", None) 

181 or tf.summary.summary_scope 

182 ) 

183 

184 # TODO(ytjing): add special case handling. 

185 with summary_scope( 

186 name, "histogram_summary", values=[data, buckets, step] 

187 ) as (tag, _): 

188 # Defer histogram bucketing logic by passing it as a callable to 

189 # write(), wrapped in a LazyTensorCreator for backwards 

190 # compatibility, so that we only do this work when summaries are 

191 # actually written. 

192 @lazy_tensor_creator.LazyTensorCreator 

193 def lazy_tensor(): 

194 return _buckets(data, buckets) 

195 

196 return tf.summary.write( 

197 tag=tag, 

198 tensor=lazy_tensor, 

199 step=step, 

200 metadata=summary_metadata, 

201 ) 

202 

203 

204def _buckets(data, bucket_count=None): 

205 """Create a TensorFlow op to group data into histogram buckets. 

206 

207 Arguments: 

208 data: A `Tensor` of any shape. Must be castable to `float64`. 

209 bucket_count: Optional non-negative `int` or scalar `int32` `Tensor`, 

210 defaults to 30. 

211 Returns: 

212 A `Tensor` of shape `[k, 3]` and type `float64`. The `i`th row is 

213 a triple `[left_edge, right_edge, count]` for a single bucket. 

214 The value of `k` is either `bucket_count` or `0` (when input data 

215 is empty). 

216 """ 

217 if bucket_count is None: 

218 bucket_count = DEFAULT_BUCKET_COUNT 

219 with tf.name_scope("buckets"): 

220 tf.debugging.assert_scalar(bucket_count) 

221 tf.debugging.assert_type(bucket_count, tf.int32) 

222 # Treat a negative bucket count as zero. 

223 bucket_count = tf.math.maximum(0, bucket_count) 

224 data = tf.reshape(data, shape=[-1]) # flatten 

225 data = tf.cast(data, tf.float64) 

226 data_size = tf.size(input=data) 

227 is_empty = tf.logical_or( 

228 tf.equal(data_size, 0), tf.less_equal(bucket_count, 0) 

229 ) 

230 

231 def when_empty(): 

232 """When input data is empty or bucket_count is zero. 

233 

234 1. If bucket_count is specified as zero, an empty tensor of shape 

235 (0, 3) will be returned. 

236 2. If the input data is empty, a tensor of shape (bucket_count, 3) 

237 of all zero values will be returned. 

238 """ 

239 return tf.zeros((bucket_count, 3), dtype=tf.float64) 

240 

241 def when_nonempty(): 

242 min_ = tf.reduce_min(input_tensor=data) 

243 max_ = tf.reduce_max(input_tensor=data) 

244 range_ = max_ - min_ 

245 has_single_value = tf.equal(range_, 0) 

246 

247 def when_multiple_values(): 

248 """When input data contains multiple values.""" 

249 bucket_width = range_ / tf.cast(bucket_count, tf.float64) 

250 offsets = data - min_ 

251 bucket_indices = tf.cast( 

252 tf.floor(offsets / bucket_width), dtype=tf.int32 

253 ) 

254 clamped_indices = tf.minimum(bucket_indices, bucket_count - 1) 

255 # Use float64 instead of float32 to avoid accumulating floating point error 

256 # later in tf.reduce_sum when summing more than 2^24 individual `1.0` values. 

257 # See https://github.com/tensorflow/tensorflow/issues/51419 for details. 

258 one_hots = tf.one_hot( 

259 clamped_indices, depth=bucket_count, dtype=tf.float64 

260 ) 

261 bucket_counts = tf.cast( 

262 tf.reduce_sum(input_tensor=one_hots, axis=0), 

263 dtype=tf.float64, 

264 ) 

265 edges = tf.linspace(min_, max_, bucket_count + 1) 

266 # Ensure edges[-1] == max_, which TF's linspace implementation does not 

267 # do, leaving it subject to the whim of floating point rounding error. 

268 edges = tf.concat([edges[:-1], [max_]], 0) 

269 left_edges = edges[:-1] 

270 right_edges = edges[1:] 

271 return tf.transpose( 

272 a=tf.stack([left_edges, right_edges, bucket_counts]) 

273 ) 

274 

275 def when_single_value(): 

276 """When input data contains a single unique value.""" 

277 # Left and right edges are the same for single value input. 

278 edges = tf.fill([bucket_count], max_) 

279 # Bucket counts are 0 except the last bucket (if bucket_count > 0), 

280 # which is `data_size`. Ensure that the resulting counts vector has 

281 # length `bucket_count` always, including the bucket_count==0 case. 

282 zeroes = tf.fill([bucket_count], 0) 

283 bucket_counts = tf.cast( 

284 tf.concat([zeroes[:-1], [data_size]], 0)[:bucket_count], 

285 dtype=tf.float64, 

286 ) 

287 return tf.transpose(a=tf.stack([edges, edges, bucket_counts])) 

288 

289 return tf.cond( 

290 has_single_value, when_single_value, when_multiple_values 

291 ) 

292 

293 return tf.cond(is_empty, when_empty, when_nonempty)