Skip to content

Commit 94d57ea

Browse files
authored
Merge branch 'master' into fix-docs
2 parents bf2d1e5 + 4d6cb5a commit 94d57ea

File tree

3 files changed

+232
-50
lines changed

3 files changed

+232
-50
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ To release a new version, please update the changelog as followed:
7070
## [Unreleased]
7171

7272
### Added
73+
- Layer
74+
- `InstanceNorm`, `InstanceNorm1d`, `InstanceNorm2d`, `InstanceNorm3d` (PR #963)
7375

7476
### Changed
7577
- remove `tl.layers.initialize_global_variables(sess)` (PR #931)
@@ -83,13 +85,16 @@ To release a new version, please update the changelog as followed:
8385

8486
### Fixed
8587
- fix docs of models @zsdonghao #957
88+
- In `BatchNorm`, keep dimensions of mean and variance to suit `channels first` (PR #963)
89+
8690

8791
### Removed
8892

8993
### Security
9094

9195
### Contributors
92-
@zsdonghao: #931
96+
- @zsdonghao: #931
97+
- @yd-yin: #963
9398

9499

95100
## [2.0.0-alpha] - 2019-05-04

docs/modules/layers.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,14 @@ Layer list
6767
batch_transformer
6868

6969
BatchNorm
70+
BatchNorm1d
71+
BatchNorm2d
72+
BatchNorm3d
7073
LocalResponseNorm
7174
InstanceNorm
75+
InstanceNorm1d
76+
InstanceNorm2d
77+
InstanceNorm3d
7278
LayerNorm
7379
GroupNorm
7480
SwitchNorm
@@ -364,6 +370,18 @@ Batch Normalization
364370
^^^^^^^^^^^^^^^^^^^^^^
365371
.. autoclass:: BatchNorm
366372

373+
Batch Normalization 1D
374+
^^^^^^^^^^^^^^^^^^^^^^^^^
375+
.. autoclass:: BatchNorm1d
376+
377+
Batch Normalization 2D
378+
^^^^^^^^^^^^^^^^^^^^^^^^^^
379+
.. autoclass:: BatchNorm2d
380+
381+
Batch Normalization 3D
382+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
383+
.. autoclass:: BatchNorm3d
384+
367385
Local Response Normalization
368386
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
369387
.. autoclass:: LocalResponseNorm
@@ -372,6 +390,18 @@ Instance Normalization
372390
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
373391
.. autoclass:: InstanceNorm
374392

393+
Instance Normalization 1D
394+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
395+
.. autoclass:: InstanceNorm1d
396+
397+
Instance Normalization 2D
398+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
399+
.. autoclass:: InstanceNorm2d
400+
401+
Instance Normalization 3D
402+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
403+
.. autoclass:: InstanceNorm3d
404+
375405
Layer Normalization
376406
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
377407
.. autoclass:: LayerNorm

tensorlayer/layers/normalization.py

Lines changed: 196 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
'BatchNorm2d',
1818
'BatchNorm3d',
1919
'InstanceNorm',
20+
'InstanceNorm1d',
21+
'InstanceNorm2d',
22+
'InstanceNorm3d',
2023
'LayerNorm',
2124
'GroupNorm',
2225
'SwitchNorm',
@@ -259,7 +262,7 @@ def build(self, inputs_shape):
259262
self.moving_var = self._get_weights("moving_var", shape=params_shape, init=self.moving_var_init)
260263

261264
def forward(self, inputs):
262-
mean, var = tf.nn.moments(inputs, self.axes)
265+
mean, var = tf.nn.moments(inputs, self.axes, keepdims=True)
263266
if self.is_train:
264267
# update moving_mean and moving_var
265268
self.moving_mean = moving_averages.assign_moving_average(
@@ -388,81 +391,225 @@ def _get_param_shape(self, inputs_shape):
388391

389392

390393
class InstanceNorm(Layer):
391-
"""The :class:`InstanceNorm` class is a for instance normalization.
394+
"""
395+
The :class:`InstanceNorm` is an instance normalization layer for both fully-connected and convolution outputs.
396+
See ``tf.nn.batch_normalization`` and ``tf.nn.moments``.
392397
393398
Parameters
394399
-----------
395400
act : activation function.
396401
The activation function of this layer.
397402
epsilon : float
398403
Eplison.
404+
beta_init : initializer or None
405+
The initializer for initializing beta, if None, skip beta.
406+
Usually you should not skip beta unless you know what happened.
407+
gamma_init : initializer or None
408+
The initializer for initializing gamma, if None, skip gamma.
409+
When the instance normalization layer is use instead of 'biases', or the next layer is linear, this can be
410+
disabled since the scaling can be done by the next layer. see `Inception-ResNet-v2 <https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_resnet_v2.py>`__
411+
num_features: int
412+
Number of features for input tensor. Useful to build layer if using InstanceNorm1d, InstanceNorm2d or InstanceNorm3d,
413+
but should be left as None if using InstanceNorm. Default None.
414+
data_format : str
415+
channels_last 'channel_last' (default) or channels_first.
399416
name : None or str
400-
A unique layer name
417+
A unique layer name.
418+
419+
420+
Examples
421+
---------
422+
With TensorLayer
423+
424+
>>> net = tl.layers.Input([None, 50, 50, 32], name='input')
425+
>>> net = tl.layers.InstanceNorm()(net)
401426
427+
Notes
428+
-----
429+
The :class:`InstanceNorm` is universally suitable for 3D/4D/5D input in static model, but should not be used
430+
in dynamic model where layer is built upon class initialization. So the argument 'num_features' should only be used
431+
for subclasses :class:`InstanceNorm1d`, :class:`InstanceNorm2d` and :class:`InstanceNorm3d`. All the three subclasses are
432+
suitable under all kinds of conditions.
402433
"""
403434

404435
def __init__(
405-
self,
406-
act=None,
407-
epsilon=1e-5,
408-
name=None, #'instan_norm',
436+
self, act=None, epsilon=0.00001, beta_init=tl.initializers.zeros(),
437+
gamma_init=tl.initializers.random_normal(mean=1.0, stddev=0.002), num_features=None,
438+
data_format='channels_last', name=None
409439
):
410-
# super(InstanceNorm, self).__init__(prev_layer=prev_layer, act=act, name=name)
411-
super().__init__(name)
440+
super(InstanceNorm, self).__init__(name=name)
412441
self.act = act
413442
self.epsilon = epsilon
443+
self.beta_init = beta_init
444+
self.gamma_init = gamma_init
445+
self.num_features = num_features
446+
self.data_format = data_format
447+
448+
if num_features is not None:
449+
if not isinstance(self, InstanceNorm1d) and not isinstance(self, InstanceNorm2d) and not isinstance(
450+
self, InstanceNorm3d):
451+
raise ValueError(
452+
"Please use InstanceNorm1d or InstanceNorm2d or InstanceNorm3d instead of InstanceNorm "
453+
"if you want to specify 'num_features'."
454+
)
455+
self.build(None)
456+
self._built = True
414457

415458
logging.info(
416-
"InstanceNorm %s: epsilon: %f act: %s" %
459+
"InstanceNorm %s: epsilon: %f act: %s " %
417460
(self.name, epsilon, self.act.__name__ if self.act is not None else 'No Activation')
418461
)
419462

463+
def __repr__(self):
464+
actstr = self.act.__name__ if self.act is not None else 'No Activation'
465+
s = '{classname}(num_features=num_features, epsilon={epsilon}' + actstr
466+
if self.name is not None:
467+
s += ', name="{name}"'
468+
s += ')'
469+
return s.format(classname=self.__class__.__name__, **self.__dict__)
470+
471+
def _get_param_shape(self, inputs_shape):
472+
if self.data_format == 'channels_last':
473+
axis = len(inputs_shape) - 1
474+
elif self.data_format == 'channels_first':
475+
axis = 1
476+
else:
477+
raise ValueError('data_format should be either %s or %s' % ('channels_last', 'channels_first'))
478+
479+
channels = inputs_shape[axis]
480+
params_shape = [1] * len(inputs_shape)
481+
params_shape[axis] = channels
482+
483+
axes = [i for i in range(len(inputs_shape)) if i != 0 and i != axis]
484+
return params_shape, axes
485+
420486
def build(self, inputs_shape):
421-
# self.scale = tf.compat.v1.get_variable(
422-
# self.name + '\scale', [inputs.get_shape()[-1]],
423-
# initializer=tf.compat.v1.initializers.truncated_normal(mean=1.0, stddev=0.02), dtype=LayersConfig.tf_dtype
424-
# )
425-
self.scale = self._get_weights(
426-
"scale", shape=[inputs_shape[-1]], init=tf.compat.v1.initializers.truncated_normal(mean=1.0, stddev=0.02)
427-
)
428-
# self.offset = tf.compat.v1.get_variable(
429-
# self.name + '\offset', [inputs.get_shape()[-1]], initializer=tf.compat.v1.initializers.constant(0.0),
430-
# dtype=LayersConfig.tf_dtype
431-
# )
432-
self.offset = self._get_weights(
433-
"offset", shape=[inputs_shape[-1]], init=tf.compat.v1.initializers.constant(0.0)
434-
)
435-
# self.add_weights([self.scale, self.offset])
487+
params_shape, self.axes = self._get_param_shape(inputs_shape)
488+
489+
self.beta, self.gamma = None, None
490+
if self.beta_init:
491+
self.beta = self._get_weights("beta", shape=params_shape, init=self.beta_init)
492+
493+
if self.gamma_init:
494+
self.gamma = self._get_weights("gamma", shape=params_shape, init=self.gamma_init)
436495

437496
def forward(self, inputs):
497+
mean, var = tf.nn.moments(inputs, self.axes, keepdims=True)
498+
outputs = batch_normalization(inputs, mean, var, self.beta, self.gamma, self.epsilon, self.data_format)
499+
if self.act:
500+
outputs = self.act(outputs)
501+
return outputs
438502

439-
mean, var = tf.nn.moments(x=inputs, axes=[1, 2], keepdims=True)
440503

441-
outputs = self.scale * tf.compat.v1.div(inputs - mean, tf.sqrt(var + self.epsilon)) + self.offset
442-
outputs = self.act(outputs)
504+
class InstanceNorm1d(InstanceNorm):
505+
"""The :class:`InstanceNorm1d` applies Instance Normalization over 3D input (a mini-instance of 1D
506+
inputs with additional channel dimension), of shape (N, L, C) or (N, C, L).
507+
See more details in :class:`InstanceNorm`.
508+
509+
Examples
510+
---------
511+
With TensorLayer
512+
513+
>>> # in static model, no need to specify num_features
514+
>>> net = tl.layers.Input([None, 50, 32], name='input')
515+
>>> net = tl.layers.InstanceNorm1d()(net)
516+
>>> # in dynamic model, build by specifying num_features
517+
>>> conv = tl.layers.Conv1d(32, 5, 1, in_channels=3)
518+
>>> bn = tl.layers.InstanceNorm1d(num_features=32)
519+
520+
"""
521+
522+
def _get_param_shape(self, inputs_shape):
523+
if self.data_format == 'channels_last':
524+
axis = 2
525+
elif self.data_format == 'channels_first':
526+
axis = 1
527+
else:
528+
raise ValueError('data_format should be either %s or %s' % ('channels_last', 'channels_first'))
529+
530+
if self.num_features is None:
531+
channels = inputs_shape[axis]
532+
else:
533+
channels = self.num_features
534+
params_shape = [1] * 3
535+
params_shape[axis] = channels
536+
537+
axes = [i for i in range(3) if i != 0 and i != axis]
538+
return params_shape, axes
443539

444-
return outputs
445540

446-
# with tf.variable_scope(name) as vs:
447-
# mean, var = tf.nn.moments(self.inputs, [1, 2], keep_dims=True)
448-
#
449-
# scale = tf.get_variable(
450-
# 'scale', [self.inputs.get_shape()[-1]],
451-
# initializer=tf.truncated_normal_initializer(mean=1.0, stddev=0.02), dtype=LayersConfig.tf_dtype
452-
# )
453-
#
454-
# offset = tf.get_variable(
455-
# 'offset', [self.inputs.get_shape()[-1]], initializer=tf.constant_initializer(0.0),
456-
# dtype=LayersConfig.tf_dtype
457-
# )
458-
#
459-
# self.outputs = scale * tf.div(self.inputs - mean, tf.sqrt(var + epsilon)) + offset
460-
# self.outputs = self._apply_activation(self.outputs)
461-
#
462-
# variables = tf.get_collection(TF_GRAPHKEYS_VARIABLES, scope=vs.name)
463-
#
464-
# self._add_layers(self.outputs)
465-
# self._add_params(variables)
541+
class InstanceNorm2d(InstanceNorm):
542+
"""The :class:`InstanceNorm2d` applies Instance Normalization over 4D input (a mini-instance of 2D
543+
inputs with additional channel dimension) of shape (N, H, W, C) or (N, C, H, W).
544+
See more details in :class:`InstanceNorm`.
545+
546+
Examples
547+
---------
548+
With TensorLayer
549+
550+
>>> # in static model, no need to specify num_features
551+
>>> net = tl.layers.Input([None, 50, 50, 32], name='input')
552+
>>> net = tl.layers.InstanceNorm2d()(net)
553+
>>> # in dynamic model, build by specifying num_features
554+
>>> conv = tl.layers.Conv2d(32, (5, 5), (1, 1), in_channels=3)
555+
>>> bn = tl.layers.InstanceNorm2d(num_features=32)
556+
557+
"""
558+
559+
def _get_param_shape(self, inputs_shape):
560+
if self.data_format == 'channels_last':
561+
axis = 3
562+
elif self.data_format == 'channels_first':
563+
axis = 1
564+
else:
565+
raise ValueError('data_format should be either %s or %s' % ('channels_last', 'channels_first'))
566+
567+
if self.num_features is None:
568+
channels = inputs_shape[axis]
569+
else:
570+
channels = self.num_features
571+
params_shape = [1] * 4
572+
params_shape[axis] = channels
573+
574+
axes = [i for i in range(4) if i != 0 and i != axis]
575+
return params_shape, axes
576+
577+
578+
class InstanceNorm3d(InstanceNorm):
579+
"""The :class:`InstanceNorm3d` applies Instance Normalization over 5D input (a mini-instance of 3D
580+
inputs with additional channel dimension) with shape (N, D, H, W, C) or (N, C, D, H, W).
581+
See more details in :class:`InstanceNorm`.
582+
583+
Examples
584+
---------
585+
With TensorLayer
586+
587+
>>> # in static model, no need to specify num_features
588+
>>> net = tl.layers.Input([None, 50, 50, 50, 32], name='input')
589+
>>> net = tl.layers.InstanceNorm3d()(net)
590+
>>> # in dynamic model, build by specifying num_features
591+
>>> conv = tl.layers.Conv3d(32, (5, 5, 5), (1, 1), in_channels=3)
592+
>>> bn = tl.layers.InstanceNorm3d(num_features=32)
593+
594+
"""
595+
596+
def _get_param_shape(self, inputs_shape):
597+
if self.data_format == 'channels_last':
598+
axis = 4
599+
elif self.data_format == 'channels_first':
600+
axis = 1
601+
else:
602+
raise ValueError('data_format should be either %s or %s' % ('channels_last', 'channels_first'))
603+
604+
if self.num_features is None:
605+
channels = inputs_shape[axis]
606+
else:
607+
channels = self.num_features
608+
params_shape = [1] * 5
609+
params_shape[axis] = channels
610+
611+
axes = [i for i in range(5) if i != 0 and i != axis]
612+
return params_shape, axes
466613

467614

468615
# FIXME : not sure about the correctness, need testing

0 commit comments

Comments
 (0)