最近遇到个需求,在使用tensoflow.data.Dataset加载dicom文件时,需要先根据文件名字划分训练集和测试集,然后再使用map函数分别将训练集和测试集逐一加载进tensorflow,其中踩了很多坑。
大体思路是使用tensor实例的numpy()函数将tensor转化为numpy数组,这个时候得到的是byte类型法的东西,再使用str函数得到字符串,但是这个字符串相比于真实字符串又会在开头和结尾多一些东西,使用[start:end]这个slice操作就可以截取出原始字符串,真正的坑在下面。
1 在map传进的实参函数提示tensor没有numpy()属性
这个坑一上网查都说是没有开启eager模式,针对tf1这个可能是完全正确的,网上很多的解决办法是开启eager模式,但是tf2.0里面默认是开启了eager模式的,又如何再去开启eager模式呢?这个问题的主要坑在于tf.data.Dataset.map函数。因为它默认走的是graph模式。Tensorflow2.x中有两种tensor:特殊的tensor和一般的EagerTensor,一般的EagerTensor是有numpy()属性的,而另一种特殊的tensor则没有。所以为了达到我们的目的(即:让map实参函数里的tensor具有numpy()属性),需要使用tf.py_functiom()函数包裹,具体代码如下:
# 定义处理函数,即我博客里说的实参函数,不加包裹函数的时候,这个函数才是map函数的实际参数,所以我这么叫它
def process(path):
path = str(path.numpy())[2:-1]
x = INPUT_DIRS + '/' + path # INPUT_DIR是我自定义的一个绝对路径
# tf.print(x)
X = dicom_process.process_single(x)
tf.print(X.shape, "*****", x)
X = tf.cast(X, dtype=tf.float32)
# tf.print(y)
return X
# 包裹函数
def wrap_process(path, y):
# tf.py_function()包裹实参函数,注意py_function函数的写法,返回值要用[]括起来,Tout是X的元素类型,无论是二维还是三维矩阵,都应该是矩阵元素的类型
[X, ] = tf.py_function(process, inp=[path], Tout=[tf.float32])
X = tf.expand_dims(X, -1) # 三维向量X*Y*Z转成X*Y*Z*1
y = tf.one_hot(y, depth=Classes, dtype=tf.int32) # 这里的Classes是我的自定义整型变量
return X, y
#sklearn里面的划分测试集和训练集的函数,这里patients是一些代表相对路径的字符串,labels是一组数字标签
train_x, test_x, train_y, test_y = train_test_split(patient_files, labels, test_size=0.2, shuffle=True)
train_data = tf.data.Dataset.from_tensor_slices((train_x, train_y))
test_data = tf.data.Dataset.from_tensor_slices((test_x, test_y))
# map函数的实参函数变成了包裹函数,不再是真正的处理函数了
train_db = train_data.map(wrap_process).batch(Batch_Size) # map函数
test_db = test_data.map(wrap_process).batch(Batch_Size)
2 劝退tensorflow
tensorflow很难调试,不支持python的断点操作,对于初学者难以上手,建议pytorch。为了搭一个三D卷积神经网络进行医学图像分类,我抄代码抄了将近一星期还没成功,总是各种报错(还有自己太菜的原因)以及无法定位报错原因。然后用pytorch去搭网络:网上抄了份代码,逐步的运行和打断点调试,基本用了两天就搭好了三维卷积模型(速度提升了这么多可能也还有之前前几天的积累),还是pytorch对新手友好点