首页 > 解决方案 > 使用 tensorflow.map_fn 创建自定义层时出现 TypeError

问题描述

我正在尝试创建一个自定义层,该层使用“DH 参数”计算机械臂的前向运动学。在我的代码中,我使用 6 个关节角度作为自定义层 (Kinematics_Physics) 的输入,并且我使用tensorflow.map_fn迭代计算输入中每​​组角度的正向运动学。我的目标是将“DH 参数”设置为可训练的权重并训练模型以优化“DH 参数”。我知道这可以很容易地使用类似库scipy.optimize或其他优化库来完成,但我想在 tensorflow 中实现这一点,所以我可以添加密集层来学习给定自定义层的逆运动学。我的代码如下:

import tensorflow as tf
print(tf.__version__)
from tensorflow import math as m
from tensorflow.keras import layers

class Kinematics_Physics(layers.Layer):

    def __init__(self,):
        super(Kinematics_Physics, self).__init__()
        # define initial variables
        self.TF_PI = tf.constant(3.1415926535897932, dtype =tf.float32)
        self.TF_180 = tf.constant(180.0, dtype =tf.float32)
        self.TINY_VALUE = tf.constant(1e-6, dtype =tf.float32)
        self.out_Pose=tf.Variable([0,0,0,0,0,0],  dtype =tf.float32, trainable = False)
        self.A_Deg = tf.Variable(0,dtype = tf.float32, trainable = False)
        self.B_Deg = tf.Variable(0,dtype = tf.float32, trainable = False)
        self.C_Deg = tf.Variable(0,dtype = tf.float32, trainable = False)
        self.sA = tf.Variable(0,dtype = tf.float32, trainable = False)
        self.cA = tf.Variable(0,dtype = tf.float32, trainable = False)
        self.b = tf.Variable(tf.zeros((4,4),dtype = tf.float32),dtype = tf.float32, trainable = False)
        
        # initial dh parameters: 6 x 4 parameters for a robotic arm with 6 joints.
        dh = tf.constant([  [0 ,    180.,   -650.,  0.],
                            [270.,   90.,      0.,  0.],
                            [800.,    0.,      0.,  0.],
                            [140.,   90.,   -908.,  0.],
                            [0.,    -96.,      0.,  0.],
                            [0.,    -65.,   260.,   0.]] ,dtype = tf.float32)
        
        # convert to trainable weights?? for further optimization 
        self.dh = tf.Variable(initial_value=dh, trainable=True)

        # initialize a buffer to calculate the the modified angles
        self.dh_processed = tf.Variable(tf.zeros(self.dh.shape,dtype = tf.float32),dtype = tf.float32, trainable = False)
        # buffer for transformation matrix
        self.trans_matrix = tf.Variable(tf.zeros((4,4),dtype = tf.float32),  dtype =tf.float32, trainable = False)


    @tf.function
    def radians(self,a):
        return m.multiply(a,(tf.divide(self.TF_PI, self.TF_180 )))
    
    @tf.function
    def degrees(self,a):
        return m.multiply(a,(tf.divide(self.TF_180 , self.TF_PI)))
    
    @tf.function
    def joint_transform(self,input_values):
        '''input: format --> 1D array = [a, α, d, θ]'''
    
        a = input_values[0] # a
        alpha = self.radians(input_values[1]) #convert degrees to radians
        d = input_values[2] # d
        theta = self.radians(input_values[3]) #convert degrees to radians θ
        
        self.trans_matrix.assign(
                                [[      m.cos(theta),             -m.sin(theta),              0,                a],
                                [m.multiply(m.sin(theta),m.cos(alpha)),  m.multiply(m.cos(theta),m.cos(alpha)), -m.sin(alpha), m.multiply(-m.sin(alpha),d)],
                                [m.multiply(m.sin(theta),m.sin(alpha)),  m.multiply(m.cos(theta),m.sin(alpha)),  m.cos(alpha),  m.multiply(m.cos(alpha),d)],
                                [        0,                             0,                     0,            1        ]])
        return self.trans_matrix
    
    @tf.function
    def to_pose(self,T):
        # converts the transformation matrix to pose [x, y, z, alpha ]
        if(m.abs(T[1,2]) <= self.TINY_VALUE and m.abs(T[2,2]) <= self.TINY_VALUE): #% singular : B = +-90 (Gimbal Lock)
            self.C_Deg.assign(tf.constant(0, dtype =tf.float32))
            self.B_Deg.assign(self.degrees(m.atan2( T[0,2] , m.divide_no_nan(T[2,2], m.cos(self.C_Deg)) ))) #convert radians to degree
            self.A_Deg.assign(self.degrees(m.atan2( T[1,0] ,  m.divide_no_nan(T[1,1], m.cos(self.C_Deg)) ))) #convert radians to degree
        
        else:
            self.A_Deg.assign(self.degrees(m.atan2(-T[0,1] , T[0,0]))) #convert radians to degree
            self.sA.assign(m.sin(self.radians(self.A_Deg)))
            self.cA.assign(m.cos(self.radians(self.A_Deg)))
            self.B_Deg.assign(self.degrees(m.atan2(T[0,2] , m.multiply(self.cA,T[0,0]) - m.multiply(self.sA,T[0,1]))))#convert radians to degree
            self.C_Deg.assign(self.degrees(m.atan2(-T[1,2] , T[2,2]))) #convert radians to degree
        
        self.out_Pose.assign([ T[0,3], T[1,3], T[2,3],self.A_Deg, self.B_Deg , self.C_Deg])
        return self.out_Pose
    
    
    @tf.function
    def forward_kinematics(self, theta):
        
        # add the dh_theta to measured theta to get the final theta
        actual_theta = m.add(self.dh[:,3] ,theta)
        
        # create new dh_table with modified theta for forward kinematics calculation
        self.dh_processed[:,0].assign(self.dh[:,0])
        self.dh_processed[:,1].assign(self.dh[:,1])
        self.dh_processed[:,2].assign(self.dh[:,2])
        self.dh_processed[:,3].assign(actual_theta)
        b = tf.eye(4, dtype = tf.float32)
        for i in tf.range(6):
            b = (tf.linalg.matmul(b,self.joint_transform(self.dh_processed[i])))
        return self.to_pose(b)
      
    def __call__(self, inputs):
        return tf.map_fn(self.forward_kinematics, inputs, parallel_iterations=True, dtype=tf.float32)
    
    def get_config(self):
        config = super(Kinematics_Physics, self).get_config()
        return config
    
# Loss function
def Euclidean_distance(y,y_hat):
    #Euclidean distance between true and predicted values 
    # ony calculates errors for the distance between two points and ignores the orientation error.
    return tf.sqrt(tf.square(y[:,0]-y_hat[:,0])+tf.square(y[:,1]-y_hat[:,1])+tf.square(y[:,2]-y_hat[:,2]))

if __name__ == "__main__":
    # create a list of six joint angles
    ang = tf.Variable([[20,10,20,10,10,20],[15,10,20,22,20,32]], dtype = tf.float32, trainable=False)
    # Call the Kinematics_physics layer
    fk = Kinematics_Physics()
    print("trainable weights: ", len(fk.trainable_weights))
    print("non-trainable weights: ", len(fk.non_trainable_weights))
    print("trainable_weights: ", fk.trainable_weights)
    print("forward kinematic transform: ", fk(ang[:10]))
    # Everything works up to this.

    # create a model using functional API
    in_= tf.keras.layers.Input(shape=(6,))
    for_kin = Kinematics_Physics()(in_)
    model = tf.keras.models.Model(inputs=in_, outputs=for_kin) 
    model.summary() 
    # Error: expected behavior => should create a model with 24 trainable parameters
    #        but the error happens in tf.map_fn of Kinematics_Physics().__call__ function
    
    opt = tf.keras.optimizers.Adam(lr=1e-3)
    model.compile(optimizer=opt, loss=Euclidean_distance)

当计算“ang”变量中给定角度的正向运动学时,程序运行正常。但模型无法编译。程序的输出如下。

2020-10-22 16:04:42.554276: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cudart64_101.dll
2.2.0-rc3
2020-10-22 16:04:45.709129: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library nvcuda.dll
2020-10-22 16:04:45.935534: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1561] Found device 0 with properties: 
pciBusID: 0000:01:00.0 name: GeForce GTX 950M computeCapability: 5.0
coreClock: 0.928GHz coreCount: 5 deviceMemorySize: 2.00GiB deviceMemoryBandwidth: 74.65GiB/s
2020-10-22 16:04:45.935854: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cudart64_101.dll
2020-10-22 16:04:45.945295: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cublas64_10.dll 
2020-10-22 16:04:45.958322: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cufft64_10.dll   
2020-10-22 16:04:45.964943: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library curand64_10.dll  
2020-10-22 16:04:45.972469: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cusolver64_10.dll
2020-10-22 16:04:45.976487: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cusparse64_10.dll
2020-10-22 16:04:45.989461: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cudnn64_7.dll
2020-10-22 16:04:45.989837: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1703] Adding visible gpu devices: 0
2020-10-22 16:04:45.990629: I tensorflow/core/platform/cpu_feature_guard.cc:143] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2
2020-10-22 16:04:46.004638: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x1b9dbcd1da0 initialized for platform Host (this does not guarantee 
that XLA will be used). Devices:
2020-10-22 16:04:46.004803: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version
2020-10-22 16:04:46.005315: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1561] Found device 0 with properties: 
pciBusID: 0000:01:00.0 name: GeForce GTX 950M computeCapability: 5.0
coreClock: 0.928GHz coreCount: 5 deviceMemorySize: 2.00GiB deviceMemoryBandwidth: 74.65GiB/s
2020-10-22 16:04:46.006569: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cudart64_101.dll
2020-10-22 16:04:46.007750: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cublas64_10.dll
2020-10-22 16:04:46.008228: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cufft64_10.dll
2020-10-22 16:04:46.008661: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library curand64_10.dll
2020-10-22 16:04:46.009383: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cusolver64_10.dll
2020-10-22 16:04:46.009760: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cusparse64_10.dll
2020-10-22 16:04:46.010063: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cudnn64_7.dll
2020-10-22 16:04:46.010487: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1703] Adding visible gpu devices: 0
2020-10-22 16:04:47.469597: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1102] Device interconnect StreamExecutor with strength 1 edge matrix:
2020-10-22 16:04:47.470084: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1108]      0 
2020-10-22 16:04:47.470593: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1121] 0:   N
2020-10-22 16:04:47.471191: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1247] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 1368 MB memory) -> physical GPU (device: 0, name: GeForce GTX 950M, pci bus id: 0000:01:00.0, compute capability: 5.0)
2020-10-22 16:04:47.501674: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x1b9f8bff2f0 initialized for platform CUDA (this does not guarantee 
that XLA will be used). Devices:
2020-10-22 16:04:47.502342: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): GeForce GTX 950M, Compute Capability 5.0
trainable weights:  1
non-trainable weights:  9
trainable_weights:  [<tf.Variable 'Variable:0' shape=(6, 4) dtype=float32, numpy=
array([[   0.,  180., -650.,    0.],
       [ 270.,   90.,    0.,    0.],
       [ 800.,    0.,    0.,    0.],
       [ 140.,   90., -908.,    0.],
       [   0.,  -96.,    0.,    0.],
       [   0.,  -65.,  260.,    0.]], dtype=float32)>]
2020-10-22 16:04:49.335812: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cublas64_10.dll
forward kinematic transform:  tf.Tensor(
[[ 548.9288   -118.251114 -527.5552     47.53804   -30.621014 -144.55815 ]
 [ 511.83533   -85.7232   -492.43372    49.052803  -46.484085 -145.28882 ]], shape=(2, 6), dtype=float32)
Traceback (most recent call last):
  File "c:/Users/ASUS/Desktop/Desktop/physics-based Calibration/physics_layer.py", line 124, in <module>
    for_kin = Kinematics_Physics()(in_)
  File "c:/Users/ASUS/Desktop/Desktop/physics-based Calibration/physics_layer.py", line 99, in __call__
    return tf.map_fn(self.forward_kinematics, inputs, parallel_iterations=True, dtype=tf.float32)
  File "C:\ProgramData\Anaconda3\envs\tf_gpu\lib\site-packages\tensorflow\python\util\deprecation.py", line 574, in new_func
    return func(*args, **kwargs)
  File "C:\ProgramData\Anaconda3\envs\tf_gpu\lib\site-packages\tensorflow\python\ops\map_fn.py", line 425, in map_fn_v2
    name=name)
  File "C:\ProgramData\Anaconda3\envs\tf_gpu\lib\site-packages\tensorflow\python\ops\map_fn.py", line 230, in map_fn
    for elem in elems_flat]
  File "C:\ProgramData\Anaconda3\envs\tf_gpu\lib\site-packages\tensorflow\python\ops\map_fn.py", line 230, in <listcomp>
    for elem in elems_flat]
  File "C:\ProgramData\Anaconda3\envs\tf_gpu\lib\site-packages\tensorflow\python\ops\tensor_array_ops.py", line 1082, in __init__
    name=name)
  File "C:\ProgramData\Anaconda3\envs\tf_gpu\lib\site-packages\tensorflow\python\ops\tensor_array_ops.py", line 717, in __init__
    self._tensor_array = [None for _ in range(size)]
TypeError: 'Tensor' object cannot be interpreted as an integer

以前有没有人遇到过类似的问题,也许我在创建这个模型或自定义层时做错了什么。有什么建议吗?

张量流版本:2.2.0-rc3,

标签: pythonpython-3.xtensorflowkerastensorflow2.0

解决方案


想出一个解决方案是一段旅程。

问题:map_fn期望一个张量,而不是张量的符号表示。

导致您的错误的问题有点难以解释。但基本上,当您创建 keras 模型时,batch_size 是未知的。这是标准行为,因此您可以为模型提供不同的批量大小。但是,该map_fn函数希望知道您提供给它的张量的形状以创建执行图。鉴于输入层的批量大小未知,这是不可能的!map_fn在 a 内使用似乎keras.Layer很难做到。

修复:使用 Tensorflow 的索引功能

好消息是我们可以通过仅参考我们感兴趣的部分来跳过批处理维度:inp[...,0]为我们提供张量的最后一个维度的第一个值,inp而无需在 batch_size 上进行索引。

不好的是,有时我们依赖批量大小,尤其是当我们需要为批量大小的每个输入提供一些可训练权重的副本时。我们可以告诉 tensorflow 通过使用来延迟这些计算tf.shape

一些评论

关于您的代码和我所做的更改的一些一般性评论:

  • 您使用了很多tf.Variable作为类属性。除非您需要在不同批次之间保持状态,否则不需要这样做。它使代码更难阅读,因为您需要使用assignintea=
  • @tf.function在每个方法类上都使用了装饰器。仅当您使用诸如for循环之类的操作时才需要它,这些操作只能急切执行并且需要在图表中执行之前进行转换。
  • 您覆盖__call__而不是call. 不要那样做!的继承__call__函数tf.keras.layers.Layer应用内部张量流逻辑来获得某些保证。call而是重新定义。
  • 除非您需要跟踪该变量,否则不要使用 tf.Variable。它们应该代表您的程序操作的共享的、持久的状态。我建议您阅读变量指南

编码

[...,]我对索引的使用有点自由。

class KinematicsPhysics(layers.Layer):
    def __init__(self,):
        super(KinematicsPhysics, self).__init__()
        # define initial variables
        self.TF_PI = tf.constant(3.1415926535897932, dtype=tf.float32)
        self.TF_180 = tf.constant(180.0, dtype=tf.float32)
        self.TINY_VALUE = tf.constant(1e-6, dtype=tf.float32)
        # initial dh parameters: 6 x 4 parameters for a robotic arm with 6 joints.
        self.INIT_DH = tf.constant(
            [
                [0, 180.0, -650.0, 0.0],
                [270.0, 90.0, 0.0, 0.0],
                [800.0, 0.0, 0.0, 0.0],
                [140.0, 90.0, -908.0, 0.0],
                [0.0, -96.0, 0.0, 0.0],
                [0.0, -65.0, 260.0, 0.0],
            ],
            dtype=tf.float32,
        )
        self.dh = tf.Variable(initial_value=self.INIT_DH, trainable=True, dtype=tf.float32)


    def compute_output_shape(self, input_shape):
        return (input_shape[0],) + (6,)

    def radians(self, a):
        return m.multiply(a, (tf.divide(self.TF_PI, self.TF_180)))

    def degrees(self, a):
        return m.multiply(a, (tf.divide(self.TF_180, self.TF_PI)))

    def joint_transform(self, input_values):
        """input: format --> 1D array = [a, α, d, θ]"""
        a = input_values[..., 0]  # a
        alpha = self.radians(input_values[..., 1])  # convert degrees to radians
        d = input_values[..., 2]  # d
        theta = self.radians(input_values[..., 3])  # convert degrees to radians θ
        row1 = tf.stack(
            [m.cos(theta), -m.sin(theta), tf.zeros(tf.shape(theta)), a], axis=-1
        )
        row2 = tf.stack(
            [
                m.multiply(m.sin(theta), m.cos(alpha)),
                m.multiply(m.cos(theta), m.cos(alpha)),
                -m.sin(alpha),
                m.multiply(-m.sin(alpha), d),
            ],
            axis=-1,
        )
        row3 = tf.stack(
            [
                m.multiply(m.sin(theta), m.sin(alpha)),
                m.multiply(m.cos(theta), m.sin(alpha)),
                m.cos(alpha),
                m.multiply(m.cos(alpha), d),
            ],
            axis=-1,
        )
        row4 = tf.stack(
            [tf.zeros(tf.shape(a)), tf.zeros(tf.shape(a)), tf.zeros(tf.shape(a)), tf.ones(tf.shape(a))],
            axis=-1,
        )
        # final size (batch_size, 6, 4 , 4)
        return tf.stack([row1, row2, row3, row4], axis=-2)

    def to_pose(self, batch):
        def if_tiny_values(T):
            a = self.degrees(m.atan2(T[..., 1, 0], m.divide_no_nan(T[..., 1, 1], 1.0)))
            b = self.degrees(m.atan2(T[..., 0, 2], m.divide_no_nan(T[..., 2, 2], 1.0)))
            c = tf.zeros(tf.shape(a))
            return tf.stack([T[..., 0, 3], T[..., 1, 3], T[..., 2, 3], a, b, c], 1)

        def if_not_tiny(T):
            a = m.atan2(-T[..., 0, 1], T[..., 0, 0])
            b = self.degrees(
                m.atan2(
                    T[..., 0, 2],
                    m.multiply(m.cos(a), T[..., 0, 0])
                    - m.multiply(m.sin(a), T[..., 0, 1]),
                )
            )
            c = self.degrees(m.atan2(-T[..., 1, 2], T[..., 2, 2]))
            a = self.degrees(a)
            return tf.stack([T[..., 0, 3], T[..., 1, 3], T[..., 2, 3], a, b, c], 1)

        ret = []
        condition = tf.logical_and(
            m.abs(batch[..., 1, 2]) <= self.TINY_VALUE,
            m.abs(batch[..., 2, 2]) <= self.TINY_VALUE,
        )
        # tf.where does not have the broadcasting capabilities of its numpy equivalent
        # see https://github.com/tensorflow/tensorflow/pull/15982
        condition = tf.expand_dims(condition, 1)
        # using tf.where instead of a if
        ret = tf.where(condition, if_tiny_values(batch), if_not_tiny(batch))

        return ret

    @tf.function
    def forward_kinematics(self, theta):
        # add the dh_theta to measured theta to get the final theta
        actual_theta = m.add(self.dh[:, 3], theta)
        # create new dh_table with modified theta for forward kinematics calculation
        # getting the first 3 column of dh
        dh_processed = tf.expand_dims(self.dh[...,:3], axis=0)
        dh_processed = tf.repeat(dh_processed, tf.shape(theta)[0], axis=0)
        # we add actual_theta as the last column. 
        dh_processed = tf.concat(
            [dh_processed, tf.expand_dims(actual_theta, -1)], 2
        )
        trans_matrix = self.joint_transform(dh_processed)
        # We create a Identity matrix for each input in the batch
        b = tf.repeat(tf.expand_dims(tf.eye(4, dtype=tf.float32),axis=0), tf.shape(theta)[0], axis=0)
        for i in tf.range(6):
            b = tf.linalg.matmul(b, trans_matrix[:, i, :, :])
        # b is shape (batch_size, 4, 4)
        return self.to_pose(b)

    def call(self, inputs):
        return self.forward_kinematics(inputs)

    def get_config(self):
        config = super(KinematicsPhysics, self).get_config()
        return config

用你的例子运行它,我们得到:

>>> import numpy as np
... ang = np.array(
        [[20, 10, 20, 10, 10, 20], [15, 10, 20, 22, 20, 32]],
         dtype=np.float32
    )
... fk = KinematicsPhysics()
... fk(ang)
<tf.Tensor: shape=(2, 6), dtype=float32, numpy=
array([[ 548.9287  , -118.2511  , -527.5551  ,   47.538036,  -30.621014,      -144.55814 ],
       [ 511.83527 ,  -85.72318 , -492.43365 ,   49.052803,  -46.48408 ,       -145.28882 ]], dtype=float32)>

如果您有任何问题,请不要犹豫,我会完善我的答案。


推荐阅读