tensorflow2.0 series: Eagle execution and Auto Graph

Keywords: Python Lambda Session Programming

Catalog

Disadvantages of static graph

The original version of tensorflow was run in the way of static graph. In this way, computation graph separated the definition and execution of computation. This is a declarative programming model

The execution mode of static graph has many advantages, but it's really inconvenient when debugging (similar to calling compiled C program, at this time we can't debug it internally). Therefore, with Eager Execution, it was first introduced in TensorFlow v1.5 and became the core API in version 2.0.

After the introduction of Eager Execution mode, TensorFlow has the same dynamic graph model capability as python. We don't need to wait for see.run(*) to see the execution results. It is convenient to debug the code at any time in IDE and view the OPs execution results. The introduction of dynamic graph also brings some new features to write tf code, which should be noted.

Eager mode

The Eager mode is a bit similar to python's command-based programming. It does not need to be compiled and run directly, which is very intuitive.

Basic features of Eagle execution

Support for numpy

The support for numpy in eager mode is very friendly. The specific features are as follows:

  • The operation of numpy can take Tensor as parameter;
  • tensorflow's mathematical operation will convert python objects and numpy's arrays into Tensor;
  • tf.Tensor.numpy method returns the ndarray of numpy

For example:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Jan  9 10:21:24 2020

@author: lxy_alex@outlook.com
"""

import numpy as np
import tensorflow as tf
tf.compat.v1.enable_eager_execution()

def example_of_tf_and_np():

    a = tf.constant([[1,2],[3,4]])
    b = tf.add(a,1)
    
    print(a)
    print(b)
    
    print('tf\'s multiply: ')
    print(a*b)
    
    c = np.multiply(a,b)
    print('numpy\'s multiply:')
    print(c)
    
    print('transfer tensor a to numpy ndarray from: ')
    print(a.numpy())
    
if __name__ == '__main__':
    example_of_tf_and_np()

Get:

tf's multiply: 
tf.Tensor(
[[ 2  6]
 [12 20]], shape=(2, 2), dtype=int32)
numpy's multiply:
[[ 2  6]
 [12 20]]
transfer tensor a to numpy ndarray from: 
[[1 2]
 [3 4]]

Although tensorflow's eager mode has good compatibility with the multidimensional data of tensor and numpy, it does not mean that the variables defined by tf.Tensor() are the same as other variables of Python. In practical use, it must be noted that the python variable and tf's tensor object cannot be confused.

Auto Graph - dynamic graph

eager mode supports both python control flow and tf dynamic flow. For tf dynamic flow, for while loop or similar loop (for, if control may be used), the form is as follows:

while x>0:
    x = x-1

In tensorflow control flow, it can be written as TF. While , loop? Vars = (x,)). However, tf.while-loop can not support infinite variables. Meanwhile, the efficiency of tensorflow calculation graph is affected by the number of while loop loops in it, so it can not be used at will.

AutoGraph uses static analysis to determine which symbols have been modified by the code in order to convert them to control rheology. Static analysis is usually performed on a single function -- Python's dynamic nature limits its effectiveness across functions.

static analysis VS dynamic flow

Visible domain of local parameter

After the local variables in the function change, the change is invisible in the main program outside the function. Similarly, in the method defined by the class, when the local variables change, the main program is invisible, unless these variables are explicitly returned as output parameters. Similarly, parameters inside a class member function are invisible outside the function.

The use of python collections data in tensorflow control flow

The control flow of tf supports most Python data structures, such as lists, dictionaries and tuples, including the namedtuple object of the collection object. However, in the control flow of tf, these variables are allowed to be fixed structure, that is, in the loop, the list cannot change the length, and the dictionary cannot increase or decrease the keys. What is a namedtuple, please refer to: https://docs.python.org/3/library/collections.htmlą·¸collections.namedtuple

def fn():
  l = []

  def loop_cond(i):
    return i < 10

  def loop_body(i):
    i = i + 1
    l.append(i)
    return i,

  tf.while_loop(
      cond=loop_cond,
      body=loop_body,
      loop_vars=(0,))

  return l

print(fn()) # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

tf.function(fn)() # ERROR

The code that can be run under eager execution reports an error under tf. Function (fn()). This is because tf.function() will start graph execution, and tf uses a special mechanism for graph execution to ensure the correctness of the operation sequence.

Another example is as follows:

def fnn():
    l = []
    for i in tf.range(10):
      l.append(i)  # Error -- illegal tensor capture!
    return l

Directly execute ll=fnn() in eager execution mode to get ll is a listener list of eager execution. However, tf.function(fnn)() is also used for execution, and the error is reported as follows:

InaccessibleTensorError: The tensor 'Tensor("placeholder:0", shape=(), dtype=int32)' cannot be accessed here: it is defined in another function or code block. Use return values, explicit Python locals or TensorFlow collections to access it. Defined in: FuncGraph(name=while_body_1396, id=5377892048); accessed from: FuncGraph(name=fnn, id=5374487632).

The correct way should be to define variables that l is tf.TensorArray(), and to invoke the write() method of TensorArray in the loop, and gradually increase the elements in TensorArray. When the local parameter L is defined, it is assigned with a length of 0 and a data type of int32, and the TensorArrary is set to be variable length (dynamic_size=True)

def fnn():
    l=tf.TensorArray(tf.int32,size=0,dynamic_size=True)
    for i in tf.range(10):
        l.write(l.size(), i)
    return l
tf.function(fnn)() 

Of course, the above fnn() function can also be directly executed in the eager execution mode (ll=fnn()), and the result is that ll is

ll
Out[188]: <tensorflow.python.ops.tensor_array_ops.TensorArray at 0x140957d90>

If python collections are included in tensorflow's process control, the index is variable, but the structure should be fixed.
For example:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Jan 18 22:27:11 2020

@author: lxy_alex@outlook.com
"""

import tensorflow as tf


if tf.executing_eagerly():
    tf.compat.v1.disable_eager_execution()
    
@tf.function
def dict_loop():
    d = {'a': tf.constant(3)}
    for i in tf.range(10):
      d = {key: value + i for key, value in d.items()}
    return d

@tf.function
def dict_loop2():
    d = {'a': tf.constant(3)}
    for i in tf.range(10):
      for key in d:
        d[key] += i  # Problem -- accessing `dict` using non-constant key
    return d
''
d = dict_loop() # d={'a': <tf.Tensor 'StatefulPartitionedCall_3:0' shape=() dtype=int32>}
//however
d2 = dict_loop2() # ERROR

In this example, the way defined in the dict UU loop2() function is wrong, while the dict UU loop() function has no problem. The official explanation is that the functional style programming method should be used. When writing code, we must pay attention to this subtle difference.

Dimension and data type of sensor in tensor flow control flow

However, in tf's graph control flow, the sensor dimension and data type need to remain unchanged, but this limitation is not valid in eager Execuion mode, because in eager mode, python control flow is used. So when you change the code from eager mode to graph mode, you must pay attention to this problem.

Dynamic calculation and static dimension

The shape and rank of the sensor are defined as follows:

The static shape is obtained by the. Shape method, and the static rank of the sensor is obtained by the. shape.rank method. When the sensor is dinamic, its shape and rank should be obtained by tf.shape(), tf.rank(), respectively.

If dynamic dimensions are needed in the code, there are two methods:
1) You can use @ tf.function decorator, for example

@tf.function(input_signature=(tf.TensorSpec(shape=(None,))))
def f(x):  # x now has dynamic shape
  if tf.shape(x)[0] >= 3:  # Builds a tf.cond
    val = x[4]  # Okay, bounds checks are skipped when the shape is dynamic
  else:
    val = some_default_value

Here, after the input_signature is assigned, the tf will skip the shape related check when executing.
2) Using python to control the flow, add a check of whether the parameter is static or dynamic, such as

if x.shape[0] is None:  # Python bool, does not use tf.cond
  # ... use x.shape here ...
else:
  # ... use tf.shape(x) here ...

Consistency of dtype and shape

In tf process, it must be noted that dtype and shape should always be consistent, such as the following error code:

x = tf.cond(
    tf.random.uniform(()) > 0.5,
    lambda: tf.constant(1, dtype=tf.int32),
    lambda: tf.constant(1, dtype=tf.float32))  # Error -- inconsistent dtypes: int32, float32

# This won't work - "x" changes dtype inside the loop.
x = tf.while_loop(
    lambda _: tf.random.uniform(()) > 0.5,
    lambda x: tf.constant(1, dtype=tf.float32),
    loop_vars=(tf.constant(1, dtype=tf.int32),))  # Error -- inconsistent dtypes: int32, float32
# Example of illegal shape change in a loop:
x = tf.constant(1,)
while tf.random.uniform(()) > 0.5:
  x = tf.constant((1, 2, 3))  # Error -- inconsistent shapes: (), (3,)


If there are None or undefined conditions in the control flow, an error will also be reported.

Accessibility of source code

In eager mode, you can execute various original codes visible at runtime, but there are also exceptions:
1) Code cannot be executed in the python interactive environment, such as ipython or jupyter lab
2) Functions with native bindings, such as code in other languages
3) Dynamic code executed with exec or eval

Inspection. Getsource (object) can be used to check the code's reachability. https://docs.python.org/3/library/inspect.html#inspect.getsource

For functions of type lambda, for example:

foo = (
 'bar',
 lambda: x)

In this case, the definition of the function is in the lambda expression, and there is no problem. In case of nesting, the called function should be declared before calling, for example:

my_lambda = lambda: x
foo = ('bar', my_lambda)

Eager training mode

Let's start with this example:

w = tf.Variable([[1.0]])
# Forward calculation to get loss
with tf.GradientTape() as tape:
  loss = w * w

grad = tape.gradient(loss, w)
print(grad)  # => tf.Tensor([[ 2.]], shape=(1, 1), dtype=float32)

This is the training mode of eager execution. In eager mode, tf.GradientTape can be used to track and record. Tape can be visualized as a tape, so reverse calculation is equivalent to rewinding. Take multiple linear regression as an example:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Jan 18 01:36:15 2020

@author: lxy_alex@outlook.com
"""
import tensorflow as tf


# A toy dataset of points around 3 * x + 2
# Add a little noise to generate training data;
NUM_EXAMPLES = 1000
training_inputs = tf.random.normal([NUM_EXAMPLES,4])
noise = tf.random.normal([NUM_EXAMPLES])

training_outputs = tf.matmul(training_inputs,[[2.7],[3.1],[5.4],[8.9]])+6.5+noise

def prediction(indata, weight, bias):
  return tf.matmul(indata, weight) + bias

# loss adopts mean square error
def loss(weights, biases):
  error = prediction(training_inputs, weights, biases) - training_outputs
  return tf.reduce_mean(tf.square(error))

# Return the derivative of loss with respect to weight and bias
def grad(weights, biases):
  # Forward calculation to get loss, and record the operation to tape for gradient calculation
  with tf.GradientTape() as tape:
    loss_value = loss(weights, biases)
  # Play the tape backward to get the gradient;
  return tape.gradient(loss_value, [weights, biases])

train_steps = 300
learning_rate = 0.01
# Start with arbitrary values for W and B on the same batch of data
W = tf.Variable([[0.],[0.],[0.],[0.]])
B = tf.Variable(0.)

print("Initial loss: {:.3f}".format(loss(W, B)))

for i in range(train_steps):
  dW, dB = grad(W, B)
  W.assign_sub(dW * learning_rate) # W = W - dW * learning_rate 
  B.assign_sub(dB * learning_rate) # B = B - dB * learning_rate
  if i % 50 == 0:
    print("Loss at step {:03d}: {:.3f}".format(i, loss(W, B)))

print("Final loss: {:.3f}".format(loss(W, B)))
print("W = {}, B = {}".format(W.numpy(), B.numpy()))

obtain

Initial loss: 161.488
Loss at step 000: 155.372
Loss at step 050: 23.209
Loss at step 100: 4.175
Loss at step 150: 1.404
Loss at step 200: 0.996
Loss at step 250: 0.936
Final loss: 0.927
W = [[2.6918666]
[3.0815856]
[5.377633 ]
[8.876133 ]], B = 6.478857517242432

Read more:

tf.Variable() and its assign ment

Operation interface of Variable: assign()

W = tf.Variable(10)
W.assign(100) 
with tf.Session() as sess: 
    sess.run(W.initializer)    
    print(W.eval(session=sess))

Is the result of printing 10 or 100???
The answer is: 10

This is because W.assign(100) does not assign a value to W. assign() is an op, so it returns an op object, which needs to be run in the Session to assign a value to W

W = tf.Variable(10)
assign_op = W.assign(100) 
with tf.Session() as sess:
     sess.run(W.initializer) 
     sess.run(assign_op) 
     print(W.eval())# >> 100

Underlined code can be omitted, because assign menu can complete initial value assignment. In fact, initializer op is a special assignment Op

https://cloud.tencent.com/developer/article/1082033

python collections

collections is a python built-in package, which is a very convenient data structure.
For details, please refer to Liao Xuefeng's python learning

111 original articles published, 118 praised, 280000 visitors+
Private letter follow

Posted by shadypalm88 on Sun, 19 Jan 2020 03:18:26 -0800