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

80 statements  

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

1# Copyright 2017 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. 

16 

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

18a triple `[left_edge, right_edge, count]`. Thus, a full histogram is 

19encoded as a tensor of dimension `[k, 3]`. 

20 

21In general, the value of `k` (the number of buckets) will be a constant, 

22like 30. There are two edge cases: if there is no data, then there are 

23no buckets (the shape is `[0, 3]`); and if there is data but all points 

24have the same value, then there is one bucket whose left and right 

25endpoints are the same (the shape is `[1, 3]`). 

26 

27NOTE: This module is in beta, and its API is subject to change, but the 

28data that it stores to disk will be supported forever. 

29""" 

30 

31 

32import numpy as np 

33 

34from tensorboard.plugins.histogram import metadata 

35from tensorboard.plugins.histogram import summary_v2 

36 

37 

38# Export V3 versions. 

39histogram = summary_v2.histogram 

40histogram_pb = summary_v2.histogram_pb 

41 

42 

43def _buckets(data, bucket_count=None): 

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

45 

46 Arguments: 

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

48 bucket_count: Optional positive `int` or scalar `int32` `Tensor`. 

49 Returns: 

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

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

52 The value of `k` is either `bucket_count` or `1` or `0`. 

53 """ 

54 # TODO(nickfelt): remove on-demand imports once dep situation is fixed. 

55 import tensorflow.compat.v1 as tf 

56 

57 if bucket_count is None: 

58 bucket_count = summary_v2.DEFAULT_BUCKET_COUNT 

59 with tf.name_scope( 

60 "buckets", values=[data, bucket_count] 

61 ), tf.control_dependencies( 

62 [tf.assert_scalar(bucket_count), tf.assert_type(bucket_count, tf.int32)] 

63 ): 

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

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

66 is_empty = tf.equal(tf.size(input=data), 0) 

67 

68 def when_empty(): 

69 return tf.constant([], shape=(0, 3), dtype=tf.float64) 

70 

71 def when_nonempty(): 

72 min_ = tf.reduce_min(input_tensor=data) 

73 max_ = tf.reduce_max(input_tensor=data) 

74 range_ = max_ - min_ 

75 is_singular = tf.equal(range_, 0) 

76 

77 def when_nonsingular(): 

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

79 offsets = data - min_ 

80 bucket_indices = tf.cast( 

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

82 ) 

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

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

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

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

87 one_hots = tf.one_hot( 

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

89 ) 

90 bucket_counts = tf.cast( 

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

92 dtype=tf.float64, 

93 ) 

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

95 left_edges = edges[:-1] 

96 right_edges = edges[1:] 

97 return tf.transpose( 

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

99 ) 

100 

101 def when_singular(): 

102 center = min_ 

103 bucket_starts = tf.stack([center - 0.5]) 

104 bucket_ends = tf.stack([center + 0.5]) 

105 bucket_counts = tf.stack( 

106 [tf.cast(tf.size(input=data), tf.float64)] 

107 ) 

108 return tf.transpose( 

109 a=tf.stack([bucket_starts, bucket_ends, bucket_counts]) 

110 ) 

111 

112 return tf.cond(is_singular, when_singular, when_nonsingular) 

113 

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

115 

116 

117def op( 

118 name, 

119 data, 

120 bucket_count=None, 

121 display_name=None, 

122 description=None, 

123 collections=None, 

124): 

125 """Create a legacy histogram summary op. 

126 

127 Arguments: 

128 name: A unique name for the generated summary node. 

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

130 bucket_count: Optional positive `int`. The output will have this 

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

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

133 same value, then there is one bucket whose left and right 

134 endpoints are the same. 

135 display_name: Optional name for this summary in TensorBoard, as a 

136 constant `str`. Defaults to `name`. 

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

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

139 collections: Optional list of graph collections keys. The new 

140 summary op is added to these collections. Defaults to 

141 `[Graph Keys.SUMMARIES]`. 

142 

143 Returns: 

144 A TensorFlow summary op. 

145 """ 

146 # TODO(nickfelt): remove on-demand imports once dep situation is fixed. 

147 import tensorflow.compat.v1 as tf 

148 

149 if display_name is None: 

150 display_name = name 

151 summary_metadata = metadata.create_summary_metadata( 

152 display_name=display_name, description=description 

153 ) 

154 with tf.name_scope(name): 

155 tensor = _buckets(data, bucket_count=bucket_count) 

156 return tf.summary.tensor_summary( 

157 name="histogram_summary", 

158 tensor=tensor, 

159 collections=collections, 

160 summary_metadata=summary_metadata, 

161 ) 

162 

163 

164def pb(name, data, bucket_count=None, display_name=None, description=None): 

165 """Create a legacy histogram summary protobuf. 

166 

167 Arguments: 

168 name: A unique name for the generated summary, including any desired 

169 name scopes. 

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

171 castable to `float`. 

172 bucket_count: Optional positive `int`. The output will have this 

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

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

175 same value, then there is one bucket whose left and right 

176 endpoints are the same. 

177 display_name: Optional name for this summary in TensorBoard, as a 

178 `str`. Defaults to `name`. 

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

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

181 

182 Returns: 

183 A `tf.Summary` protobuf object. 

184 """ 

185 # TODO(nickfelt): remove on-demand imports once dep situation is fixed. 

186 import tensorflow.compat.v1 as tf 

187 

188 if bucket_count is None: 

189 bucket_count = summary_v2.DEFAULT_BUCKET_COUNT 

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

191 if data.size == 0: 

192 buckets = np.array([]).reshape((0, 3)) 

193 else: 

194 min_ = np.min(data) 

195 max_ = np.max(data) 

196 range_ = max_ - min_ 

197 if range_ == 0: 

198 center = min_ 

199 buckets = np.array([[center - 0.5, center + 0.5, float(data.size)]]) 

200 else: 

201 bucket_width = range_ / bucket_count 

202 offsets = data - min_ 

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

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

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

206 0, bucket_count 

207 ) # broadcast 

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

209 one_hots.shape, 

210 (data.size, bucket_count), 

211 ) 

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

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

214 left_edges = edges[:-1] 

215 right_edges = edges[1:] 

216 buckets = np.array( 

217 [left_edges, right_edges, bucket_counts] 

218 ).transpose() 

219 tensor = tf.make_tensor_proto(buckets, dtype=tf.float64) 

220 

221 if display_name is None: 

222 display_name = name 

223 summary_metadata = metadata.create_summary_metadata( 

224 display_name=display_name, description=description 

225 ) 

226 tf_summary_metadata = tf.SummaryMetadata.FromString( 

227 summary_metadata.SerializeToString() 

228 ) 

229 

230 summary = tf.Summary() 

231 summary.value.add( 

232 tag="%s/histogram_summary" % name, 

233 metadata=tf_summary_metadata, 

234 tensor=tensor, 

235 ) 

236 return summary