Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/keras/src/layers/preprocessing/normalization.py: 19%

112 statements  

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

1# Copyright 2019 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"""Normalization preprocessing layer.""" 

16 

17 

18import numpy as np 

19import tensorflow.compat.v2 as tf 

20 

21from keras.src import backend 

22from keras.src.engine import base_preprocessing_layer 

23from keras.src.layers.preprocessing import preprocessing_utils as utils 

24 

25# isort: off 

26from tensorflow.python.util.tf_export import keras_export 

27 

28 

29@keras_export( 

30 "keras.layers.Normalization", 

31 "keras.layers.experimental.preprocessing.Normalization", 

32) 

33class Normalization(base_preprocessing_layer.PreprocessingLayer): 

34 """A preprocessing layer which normalizes continuous features. 

35 

36 This layer will shift and scale inputs into a distribution centered around 

37 0 with standard deviation 1. It accomplishes this by precomputing the mean 

38 and variance of the data, and calling `(input - mean) / sqrt(var)` at 

39 runtime. 

40 

41 The mean and variance values for the layer must be either supplied on 

42 construction or learned via `adapt()`. `adapt()` will compute the mean and 

43 variance of the data and store them as the layer's weights. `adapt()` should 

44 be called before `fit()`, `evaluate()`, or `predict()`. 

45 

46 For an overview and full list of preprocessing layers, see the preprocessing 

47 [guide](https://www.tensorflow.org/guide/keras/preprocessing_layers). 

48 

49 Args: 

50 axis: Integer, tuple of integers, or None. The axis or axes that should 

51 have a separate mean and variance for each index in the shape. For 

52 example, if shape is `(None, 5)` and `axis=1`, the layer will track 5 

53 separate mean and variance values for the last axis. If `axis` is set 

54 to `None`, the layer will normalize all elements in the input by a 

55 scalar mean and variance. When `-1` the last axis of the 

56 input is assumed to be a feature dimension and is normalized per 

57 index. Note that in the specific case of batched scalar inputs where 

58 the only axis is the batch axis, the default will normalize each index 

59 in the batch separately. In this case, consider passing `axis=None`. 

60 Defaults to `-1`. 

61 mean: The mean value(s) to use during normalization. The passed value(s) 

62 will be broadcast to the shape of the kept axes above; if the value(s) 

63 cannot be broadcast, an error will be raised when this layer's 

64 `build()` method is called. 

65 variance: The variance value(s) to use during normalization. The passed 

66 value(s) will be broadcast to the shape of the kept axes above; if the 

67 value(s) cannot be broadcast, an error will be raised when this 

68 layer's `build()` method is called. 

69 invert: If True, this layer will apply the inverse transformation 

70 to its inputs: it would turn a normalized input back into its 

71 original form. 

72 

73 Examples: 

74 

75 Calculate a global mean and variance by analyzing the dataset in `adapt()`. 

76 

77 >>> adapt_data = np.array([1., 2., 3., 4., 5.], dtype='float32') 

78 >>> input_data = np.array([1., 2., 3.], dtype='float32') 

79 >>> layer = tf.keras.layers.Normalization(axis=None) 

80 >>> layer.adapt(adapt_data) 

81 >>> layer(input_data) 

82 <tf.Tensor: shape=(3,), dtype=float32, numpy= 

83 array([-1.4142135, -0.70710677, 0.], dtype=float32)> 

84 

85 Calculate a mean and variance for each index on the last axis. 

86 

87 >>> adapt_data = np.array([[0., 7., 4.], 

88 ... [2., 9., 6.], 

89 ... [0., 7., 4.], 

90 ... [2., 9., 6.]], dtype='float32') 

91 >>> input_data = np.array([[0., 7., 4.]], dtype='float32') 

92 >>> layer = tf.keras.layers.Normalization(axis=-1) 

93 >>> layer.adapt(adapt_data) 

94 >>> layer(input_data) 

95 <tf.Tensor: shape=(1, 3), dtype=float32, numpy= 

96 array([-1., -1., -1.], dtype=float32)> 

97 

98 Pass the mean and variance directly. 

99 

100 >>> input_data = np.array([[1.], [2.], [3.]], dtype='float32') 

101 >>> layer = tf.keras.layers.Normalization(mean=3., variance=2.) 

102 >>> layer(input_data) 

103 <tf.Tensor: shape=(3, 1), dtype=float32, numpy= 

104 array([[-1.4142135 ], 

105 [-0.70710677], 

106 [ 0. ]], dtype=float32)> 

107 

108 Use the layer to de-normalize inputs (after adapting the layer). 

109 

110 >>> adapt_data = np.array([[0., 7., 4.], 

111 ... [2., 9., 6.], 

112 ... [0., 7., 4.], 

113 ... [2., 9., 6.]], dtype='float32') 

114 >>> input_data = np.array([[1., 2., 3.]], dtype='float32') 

115 >>> layer = tf.keras.layers.Normalization(axis=-1, invert=True) 

116 >>> layer.adapt(adapt_data) 

117 >>> layer(input_data) 

118 <tf.Tensor: shape=(1, 3), dtype=float32, numpy= 

119 array([2., 10., 8.], dtype=float32)> 

120 """ 

121 

122 def __init__( 

123 self, axis=-1, mean=None, variance=None, invert=False, **kwargs 

124 ): 

125 super().__init__(**kwargs) 

126 base_preprocessing_layer.keras_kpl_gauge.get_cell("Normalization").set( 

127 True 

128 ) 

129 

130 # Standardize `axis` to a tuple. 

131 if axis is None: 

132 axis = () 

133 elif isinstance(axis, int): 

134 axis = (axis,) 

135 else: 

136 axis = tuple(axis) 

137 self.axis = axis 

138 

139 # Set `mean` and `variance` if passed. 

140 if isinstance(mean, tf.Variable): 

141 raise ValueError( 

142 "Normalization does not support passing a Variable " 

143 "for the `mean` init arg." 

144 ) 

145 if isinstance(variance, tf.Variable): 

146 raise ValueError( 

147 "Normalization does not support passing a Variable " 

148 "for the `variance` init arg." 

149 ) 

150 if (mean is not None) != (variance is not None): 

151 raise ValueError( 

152 "When setting values directly, both `mean` and `variance` " 

153 "must be set. Got mean: {} and variance: {}".format( 

154 mean, variance 

155 ) 

156 ) 

157 self.input_mean = mean 

158 self.input_variance = variance 

159 self.invert = invert 

160 

161 def build(self, input_shape): 

162 super().build(input_shape) 

163 

164 if isinstance(input_shape, (list, tuple)) and all( 

165 isinstance(shape, tf.TensorShape) for shape in input_shape 

166 ): 

167 raise ValueError( 

168 "Normalization only accepts a single input. If you are " 

169 "passing a python list or tuple as a single input, " 

170 "please convert to a numpy array or `tf.Tensor`." 

171 ) 

172 

173 input_shape = tf.TensorShape(input_shape).as_list() 

174 ndim = len(input_shape) 

175 

176 if any(a < -ndim or a >= ndim for a in self.axis): 

177 raise ValueError( 

178 "All `axis` values must be in the range [-ndim, ndim). " 

179 "Found ndim: `{}`, axis: {}".format(ndim, self.axis) 

180 ) 

181 

182 # Axes to be kept, replacing negative values with positive equivalents. 

183 # Sorted to avoid transposing axes. 

184 self._keep_axis = sorted([d if d >= 0 else d + ndim for d in self.axis]) 

185 # All axes to be kept should have known shape. 

186 for d in self._keep_axis: 

187 if input_shape[d] is None: 

188 raise ValueError( 

189 "All `axis` values to be kept must have known shape. " 

190 "Got axis: {}, " 

191 "input shape: {}, with unknown axis at index: {}".format( 

192 self.axis, input_shape, d 

193 ) 

194 ) 

195 # Axes to be reduced. 

196 self._reduce_axis = [d for d in range(ndim) if d not in self._keep_axis] 

197 # 1 if an axis should be reduced, 0 otherwise. 

198 self._reduce_axis_mask = [ 

199 0 if d in self._keep_axis else 1 for d in range(ndim) 

200 ] 

201 # Broadcast any reduced axes. 

202 self._broadcast_shape = [ 

203 input_shape[d] if d in self._keep_axis else 1 for d in range(ndim) 

204 ] 

205 mean_and_var_shape = tuple(input_shape[d] for d in self._keep_axis) 

206 

207 if self.input_mean is None: 

208 self.adapt_mean = self.add_weight( 

209 name="mean", 

210 shape=mean_and_var_shape, 

211 dtype=self.compute_dtype, 

212 initializer="zeros", 

213 trainable=False, 

214 ) 

215 self.adapt_variance = self.add_weight( 

216 name="variance", 

217 shape=mean_and_var_shape, 

218 dtype=self.compute_dtype, 

219 initializer="ones", 

220 trainable=False, 

221 ) 

222 self.count = self.add_weight( 

223 name="count", 

224 shape=(), 

225 dtype=tf.int64, 

226 initializer="zeros", 

227 trainable=False, 

228 ) 

229 self.finalize_state() 

230 else: 

231 # In the no adapt case, make constant tensors for mean and variance 

232 # with proper broadcast shape for use during call. 

233 mean = self.input_mean * np.ones(mean_and_var_shape) 

234 variance = self.input_variance * np.ones(mean_and_var_shape) 

235 mean = tf.reshape(mean, self._broadcast_shape) 

236 variance = tf.reshape(variance, self._broadcast_shape) 

237 self.mean = tf.cast(mean, self.compute_dtype) 

238 self.variance = tf.cast(variance, self.compute_dtype) 

239 

240 # We override this method solely to generate a docstring. 

241 def adapt(self, data, batch_size=None, steps=None): 

242 """Computes the mean and variance of values in a dataset. 

243 

244 Calling `adapt()` on a `Normalization` layer is an alternative to 

245 passing in `mean` and `variance` arguments during layer construction. A 

246 `Normalization` layer should always either be adapted over a dataset or 

247 passed `mean` and `variance`. 

248 

249 During `adapt()`, the layer will compute a `mean` and `variance` 

250 separately for each position in each axis specified by the `axis` 

251 argument. To calculate a single `mean` and `variance` over the input 

252 data, simply pass `axis=None`. 

253 

254 In order to make `Normalization` efficient in any distribution context, 

255 the computed mean and variance are kept static with respect to any 

256 compiled `tf.Graph`s that call the layer. As a consequence, if the layer 

257 is adapted a second time, any models using the layer should be 

258 re-compiled. For more information see 

259 `tf.keras.layers.experimental.preprocessing.PreprocessingLayer.adapt`. 

260 

261 `adapt()` is meant only as a single machine utility to compute layer 

262 state. To analyze a dataset that cannot fit on a single machine, see 

263 [Tensorflow Transform]( 

264 https://www.tensorflow.org/tfx/transform/get_started) 

265 for a multi-machine, map-reduce solution. 

266 

267 Arguments: 

268 data: The data to train on. It can be passed either as a 

269 `tf.data.Dataset`, or as a numpy array. 

270 batch_size: Integer or `None`. 

271 Number of samples per state update. 

272 If unspecified, `batch_size` will default to 32. 

273 Do not specify the `batch_size` if your data is in the 

274 form of datasets, generators, or `keras.utils.Sequence` instances 

275 (since they generate batches). 

276 steps: Integer or `None`. 

277 Total number of steps (batches of samples) 

278 When training with input tensors such as 

279 TensorFlow data tensors, the default `None` is equal to 

280 the number of samples in your dataset divided by 

281 the batch size, or 1 if that cannot be determined. If x is a 

282 `tf.data` dataset, and 'steps' is None, the epoch will run until 

283 the input dataset is exhausted. When passing an infinitely 

284 repeating dataset, you must specify the `steps` argument. This 

285 argument is not supported with array inputs. 

286 """ 

287 super().adapt(data, batch_size=batch_size, steps=steps) 

288 

289 def update_state(self, data): 

290 if self.input_mean is not None: 

291 raise ValueError( 

292 "Cannot `adapt` a Normalization layer that is initialized with " 

293 "static `mean` and `variance`, " 

294 "you passed mean {} and variance {}.".format( 

295 self.input_mean, self.input_variance 

296 ) 

297 ) 

298 

299 if not self.built: 

300 raise RuntimeError("`build` must be called before `update_state`.") 

301 

302 data = self._standardize_inputs(data) 

303 data = tf.cast(data, self.adapt_mean.dtype) 

304 batch_mean, batch_variance = tf.nn.moments(data, axes=self._reduce_axis) 

305 batch_shape = tf.shape(data, out_type=self.count.dtype) 

306 if self._reduce_axis: 

307 batch_reduce_shape = tf.gather(batch_shape, self._reduce_axis) 

308 batch_count = tf.reduce_prod(batch_reduce_shape) 

309 else: 

310 batch_count = 1 

311 

312 total_count = batch_count + self.count 

313 batch_weight = tf.cast(batch_count, dtype=self.compute_dtype) / tf.cast( 

314 total_count, dtype=self.compute_dtype 

315 ) 

316 existing_weight = 1.0 - batch_weight 

317 

318 total_mean = ( 

319 self.adapt_mean * existing_weight + batch_mean * batch_weight 

320 ) 

321 # The variance is computed using the lack-of-fit sum of squares 

322 # formula (see 

323 # https://en.wikipedia.org/wiki/Lack-of-fit_sum_of_squares). 

324 total_variance = ( 

325 self.adapt_variance + (self.adapt_mean - total_mean) ** 2 

326 ) * existing_weight + ( 

327 batch_variance + (batch_mean - total_mean) ** 2 

328 ) * batch_weight 

329 self.adapt_mean.assign(total_mean) 

330 self.adapt_variance.assign(total_variance) 

331 self.count.assign(total_count) 

332 

333 def reset_state(self): 

334 if self.input_mean is not None or not self.built: 

335 return 

336 

337 self.adapt_mean.assign(tf.zeros_like(self.adapt_mean)) 

338 self.adapt_variance.assign(tf.ones_like(self.adapt_variance)) 

339 self.count.assign(tf.zeros_like(self.count)) 

340 

341 def finalize_state(self): 

342 if self.input_mean is not None or not self.built: 

343 return 

344 

345 # In the adapt case, we make constant tensors for mean and variance with 

346 # proper broadcast shape and dtype each time `finalize_state` is called. 

347 self.mean = tf.reshape(self.adapt_mean, self._broadcast_shape) 

348 self.mean = tf.cast(self.mean, self.compute_dtype) 

349 self.variance = tf.reshape(self.adapt_variance, self._broadcast_shape) 

350 self.variance = tf.cast(self.variance, self.compute_dtype) 

351 

352 def call(self, inputs): 

353 inputs = self._standardize_inputs(inputs) 

354 # The base layer automatically casts floating-point inputs, but we 

355 # explicitly cast here to also allow integer inputs to be passed 

356 inputs = tf.cast(inputs, self.compute_dtype) 

357 if self.invert: 

358 return self.mean + ( 

359 inputs * tf.maximum(tf.sqrt(self.variance), backend.epsilon()) 

360 ) 

361 else: 

362 return (inputs - self.mean) / tf.maximum( 

363 tf.sqrt(self.variance), backend.epsilon() 

364 ) 

365 

366 def compute_output_shape(self, input_shape): 

367 return input_shape 

368 

369 def compute_output_signature(self, input_spec): 

370 return input_spec 

371 

372 def get_config(self): 

373 config = super().get_config() 

374 config.update( 

375 { 

376 "axis": self.axis, 

377 "invert": self.invert, 

378 "mean": utils.listify_tensors(self.input_mean), 

379 "variance": utils.listify_tensors(self.input_variance), 

380 } 

381 ) 

382 return config 

383 

384 def _standardize_inputs(self, inputs): 

385 inputs = tf.convert_to_tensor(inputs) 

386 if inputs.dtype != self.compute_dtype: 

387 inputs = tf.cast(inputs, self.compute_dtype) 

388 return inputs 

389 

390 def load_own_variables(self, store): 

391 # Ensure that we call finalize_state after variable loading. 

392 super().load_own_variables(store) 

393 self.finalize_state() 

394