How to redefine everything from numpy, and some actually useful tricks for part assignment, saving custom layers, etc.

We’re talking about creating custom TensorFlow layers and models in:
TensorFlow v2.5.0.
No wasting time with introductions, let’s go:
tf.map_fn , e.g. converting vectors of size n in a batch (batch_size x n) to diagonal matrices of size (batch_size x n x n) :mats = tf.map\_fn(lambda vec: tf.linalg.tensor\_diag(vec), vecs)
tf.matmul, the batch_size is already taken care of:The inputs must, following any transpositions, be tensors of rank >= 2 where the inner 2 dimensions specify valid matrix multiplication dimensions, and any further outer dimensions specify matching batch size.
np.sum(x) →tf.map.reduce_sum(x)np.dot(x,y) →tf.tensordot(x,y,1)A.b : np.dot(A,b) →tf.linalg.matvec(A,b)np.sin →tf.math.sin(batch_size x n x m)→(batch_size x m x n) : np.transpose(x, axes=(0,2,1)) →tf.transpose(x, perm=[0,2,1])np.diag(x) →tf.linalg.tensor_diag(x)np.concatenate((A,B),axes=0) →tf.concat([A,B],1)[[A,B],[C,D]] into a single matrix:tmp1 = tf.concat([A,B],2)
tmp2 = tf.concat([C,D],2)
mat = tf.concat([tmp1,tmp2],1)
vec of size n (makes a matrix n x n): tf.tensordot(vec,vec,axes=0) except the vectors usually come in a batch vecs of size (batch_size x n) , so we need to map:mats = tf.map\_fn(lambda vec: tf.tensordot(vec,vec,=0),vecs)
np.zeros(n) → tf.zeros_like(n) . Note that to avoid the error “you should use tf.zeros_like instead of tf.zeros since the former does not require the size to be known until runtime, see also here.In numpy we can just write:
a = np.random.rand(3)
a[0] = 5.0
This doesn’t work in TensorFlow:
a = tf.Variable(initial\_value=np.random.rand(3),dtype='float32')
a[0] = 5.0
# TypeError: 'ResourceVariable' object does not support item assignment
Instead, you can add/subtract unit vectors/matrices/tensors with the appropriate values. The tf.one_hot function can be used to construct these:
e = tf.one\_hot(indices=0,depth=3,dtype='float32')
# <tf.Tensor: shape=(3,), dtype=float32, numpy=array([1., 0., 0.], dtype=float32)>
and then change the value like this:
a = a + (new\_val - old\_val) * e
where a is the TF variable, new_val and old_val are floats, and e is the unit vector.
For example, in the previous example:
Same as the vector example, but to construct the unit matrices you can use this helper function:
which you can adapt to higher order tensors as well.
Use tf.shape(x) instead of x.shape . You can read this discussion here: or:
Do not use .shape; rather use tf.shape. However, h = tf.shape(x)[1]; w = tf.shape(x)[2] will let h, w be symbolic or graph-mode tensors (integer) that will contain a dimension of x. The value will be determined runtime. In such a case, tf.reshape(x, [-1, w, h]) will produce a (symbolic) tensor of shape [?, ?, ?] (still unknown) whose tensor shape will be known on runtime.
Model if you want to use fit or evaluate or something similar, otherwise Layer .
From the docs:
Typically you inherit from keras.Model when you need the model methods like: Model.fit,Model.evaluate, and Model.save (see Custom Keras layers and models for details). One other feature provided by keras.Model (instead of keras.layers.Layer) is that in addition to tracking variables, a keras.Model also tracks its internal layers, making them easier to inspect.
super in the constructor.call method.get_config and from_config — they are needed often for saving the layer, e.g. if save_traces=False in the model.@tf.keras.utils.register_keras_serializable(="my_package") should be added at the top of the class. From the docs:This decorator injects the decorated class or function into the Keras custom object dictionary, so that it can be serialized and deserialized without needing an entry in the user-provided custom object dict.
This really means that if you do not put it, you must later load your model containing the layer like this Keras documentation describes:
which is kind of a pain.
If you have correctly defined get_config and from_config for each of your layers and models as above, you can just save using:
model.save("model", save\_traces=False)
# Or just save the weights
model.save\_weights("model\_weights")
Note that you do not need save_traces if you have defined those correctly. This also makes the saved models much smaller!
To load, just use:
model = tf.keras.models.load\_model("model")
# Or just load the weights for an already existing model
model.load\_weights("model\_weights")
Note that we don’t need to specify and custom_objects if we correctly used the
@tf.keras.utils.register\_keras\_serializable(="my\_package")
decorator everywhere.
Similar to how save_traces reduces the size of saved models, you can use write_graph=False in the TensorBoard callback to reduce the size of these files:
logdir = os.path.join("logs",datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
tensorboard\_callback = tf.keras.callbacks.TensorBoard(logdir,
histogram\_freq=1,
write\_graph=False )
Or, for a ModelCheckpoint , you can use save_weights_only=True to just save the weights and not the model at all:
val\_checkpoint = tf.keras.callbacks.ModelCheckpoint(
'trained',
='val\_loss',
=1,
=True,
=True,
='auto',
=1
)
Hope this starter-set of tricks helped!
Oliver K. Ernst
July 8, 2021