python - 使用 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,
解决方案
想出一个解决方案是一段旅程。
问题:map_fn
期望一个张量,而不是张量的符号表示。
导致您的错误的问题有点难以解释。但基本上,当您创建 keras 模型时,batch_size 是未知的。这是标准行为,因此您可以为模型提供不同的批量大小。但是,该map_fn
函数希望知道您提供给它的张量的形状以创建执行图。鉴于输入层的批量大小未知,这是不可能的!map_fn
在 a 内使用似乎keras.Layer
很难做到。
修复:使用 Tensorflow 的索引功能
好消息是我们可以通过仅参考我们感兴趣的部分来跳过批处理维度:inp[...,0]
为我们提供张量的最后一个维度的第一个值,inp
而无需在 batch_size 上进行索引。
不好的是,有时我们依赖批量大小,尤其是当我们需要为批量大小的每个输入提供一些可训练权重的副本时。我们可以告诉 tensorflow 通过使用来延迟这些计算tf.shape
一些评论
关于您的代码和我所做的更改的一些一般性评论:
- 您使用了很多
tf.Variable
作为类属性。除非您需要在不同批次之间保持状态,否则不需要这样做。它使代码更难阅读,因为您需要使用assign
intea=
- 您
@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)>
如果您有任何问题,请不要犹豫,我会完善我的答案。
推荐阅读
- php - Wordpress主题开发:包含文件中的变量在functions.php中使用时为空白
- java - 如何使用appium将PC上的文件放在设备(Android)上的特定位置
- reactjs - 当状态改变时,React 不会用新的 props 重新渲染组件
- r - 特定列上 R 程序中的 BubbleSort
- scheduling - 内存中的 MassTransit 预定消息
- java - Android Studio 在运行 Gradle 时使用以下 JDK 位置
- python - 设置按钮的宽度会删除文本标签
- javascript - 使用 Intl.NumberFormat() 构造函数求和值
- c - 可变长度数组和动态内存分配有什么区别?
- c - 如何从C中的行中获取第一个和最后一个单词?