简体   繁体   English

将函数应用于 3D、2 通道 Keras 张量的每个元素

[英]Apply a function to each element of a 3D, 2 channel Keras tensor

I have a model that processes 2 input 3D images of the same size, A and B , for use in a more classical function to attempt to increase performance of this function.我有一个模型可以处理 2 个相同大小的输入 3D 图像AB ,用于更经典的函数以尝试提高此函数的性能。 In order to properly train the model I need to apply the function to the result of each run.为了正确训练模型,我需要将该函数应用于每次运行的结果。 The function itself takes 2 values, which correspond to the values in A and B at the same coordinate p .该函数本身采用 2 个值,它们对应于AB中同一坐标p处的值。 This result is to be stored in a 3D image, C , of the same size as A and B at point p .此结果将存储在 3D 图像C ,其大小与点p处的AB相同。 Classical implementations of this would perform a for loop over all the coordinates and apply the function for each pair.这个的经典实现将在所有坐标上执行for循环并为每一对应用该函数。 Unfortunately, this approach does not work for training a Keras model as the output of the function has to feed back to the weights of the previous layers.不幸的是,这种方法不适用于训练 Keras 模型,因为函数的输出必须反馈到前一层的权重。

Input -(A, B)-> Model -(A', B')-> Function(A'[p], B'[p]) -(C[p])-> Result

I have attempted to write a custom Keras layer for this.我试图为此编写一个自定义的 Keras 层。 This layer accepts a 4D tensor (channel, z, y, x) and should return a tensor with shape (1, z, y, x) .该层接受一个 4D 张量(channel, z, y, x)并且应该返回一个形状为(1, z, y, x)的张量。

Currently this looks like:目前这看起来像:

# imports

def function(x: [float, float]) -> float:
    # a -> x[0], b -> x[1]
    # Calculate
    return c

class CustomLayer(Layer):
    # ... __init__ and build
    def call(self, inputs, **kwargs):
        # All samples, channel n ([::][n])
        # We stack the tensors in such a way because map_fn() maps the top most axis to the function.
        # This way a tensor of shape (n_voxels, 2) is created and the values are delivered in pairs to the function
        map_input = K.stack([K.flatten(inputs[::][0]), K.flatten(inputs[::][1]), axis=1])
        result = K.map_fn(lambda x: function(x), map_input)
        result = K.reshape(result, K.constant([-1, 1, inputs.shape[2], inputs.shape[3], inputs.shape[4]], dtype=tf.int32))
        return result

Unfortunately, this method has severely slowed down the training.不幸的是,这种方法严重拖慢了训练速度。 Whereas the model without the custom layer at the end took around 45 minutes to train per epoch, the model with the custom layer takes about 120 hours per epoch.最后,没有自定义层的模型每个 epoch 需要大约 45 分钟的时间来训练,而带有自定义层的模型每个 epoch 需要大约 120小时

The model and the function on their own can perform the required task with a significant error, however I wanted to see if I could combine the two for better results.模型和函数本身可以执行所需的任务,但会出现重大错误,但是我想看看是否可以将两者结合起来以获得更好的结果。


The real-life use in my case is the decomposition of materials from Dual Energy CT . 在我的案例中,实际用途是从Dual Energy CT分解材料。 You can calculate the fraction of materials for each voxel by assuming 3 known materials mat1 , mat2 , mat3 and an unknown sample sample . 您可以通过假设 3 个已知材料mat1mat2mat3和未知样本sample来计算每个体素的材料mat1

With some calculations you can then decompose this unknown sample into fractions of each known material f1 , f2 , f3 ( f1 + f2 + f3 == 1.0).通过一些计算,您可以将这个未知样本分解为每个已知材料f1f2f3分数( f1 + f2 + f3 == 1.0)。 We are only interested in f3 , so the calculations for the other fractions have been omitted.我们只对f3感兴趣,因此省略了其他分数的计算。

Actual code:实际代码:

 """ DECT Decomposition Layer """ import numpy as np import tensorflow as tf from keras import backend as K from keras.layers import Layer from keras.constraints import MinMaxNorm def _intersect(line1_start, line1_end, line2_start, line2_end, ): """ Find the intersection point between 2 lines """ # https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection # Tensorflow's tensors need a little more lines to unpack x1 = line1_start[0] y1 = line1_start[1] x2 = line1_end[0] y2 = line1_end[1] x3 = line2_start[0] y3 = line2_start[1] x4 = line2_end[0] y4 = line2_end[1] px = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4) px /= (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) py = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4) py /= (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) return K.stack([px, py]) def _decomp(sample, mat1, mat2, mat3): """ Decomposition of a sample into 1 fraction of material 3 """ # Calculate the sample lines' ends sample3 = sample + (mat2 - mat3) # Calculate the intersection points between the sample lines and triangle sides intersect3 = _intersect(sample, sample3, mat1, mat2) # Find out how far along the sample line the intersection is f3 = tf.norm(sample - intersect3) / tf.norm(sample - sample3) return f3 class DectDecompoLayer(Layer): def __init__(self, mat1, mat2, mat3, **kwargs): self.mat1 = K.constant(mat1) self.mat2 = K.constant(mat2) self.mat3 = K.constant(mat3) super(DectDecompoLayer, self).__init__(**kwargs) def build(self, input_shape): super(DectDecompoLayer, self).build(input_shape) def call(self, inputs, **kwargs): map_input = K.stack([K.flatten(inputs[::][0]), K.flatten(inputs[::][1])], axis=1) result = K.map_fn(lambda x: _decomp(x, self.mat1, self.mat2, self.mat3), map_input) result = K.reshape(result, K.constant([-1, 1, inputs.shape[2], inputs.shape[3], inputs.shape[4]], dtype=tf.int32)) return result def compute_output_shape(self, input_shape): return input_shape[0], 1, input_shape[2], input_shape[3], input_shape[4]

Ok.好的。 The first thing that is very strange is that you are taking "samples", not "channels".非常奇怪的第一件事是您正在采取“样本”,而不是“渠道”。

The command inputs[::] returns exactly inputs , and inputs[::][0] is equal to inputs[0] .命令inputs[::]返回精确的inputsinputs[::][0]等于inputs[0]

So, you are training only two samples, no matter how big your batch size is.因此,无论批量大小有多大,您都只训练两个样本。

That said, all you need is something like:也就是说,您所需要的只是:

  • Assuming inputs with shape (batch, 2, size, size, size)假设inputs具有形状(batch, 2, size, size, size)
  • Assuming matN with shape (1, 2, size, size, size) , exactly, or (batch, 2, size, size, size)假设matN的形状为(1, 2, size, size, size) ,确切地说,或(batch, 2, size, size, size)
def call(self, inputs, **kwargs): #shape (batch, 2, size, size, size)
    sample3 = inputs + (self.mat2 - self.mat3) 

    #all shapes (batch, size, size, size)
    x1 = inputs[:,0]
    y1 = inputs[:,1]
    x2 = sample3[:,0]
    y2 = sample3[:,1]

    #all shapes (1, size, size, size) or (batch, size, size, size)
    x3 = self.mat1[:,0]
    y3 = self.mat2[:,1]
    x4 = self.mat2[:,0]
    y4 = self.mat2[:,1]


    #all shapes (batch, size, size, size)
    px = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)
    px /= (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
    py = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)
    py /= (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)

    #proceed with the rest

Warning, you may have division by zero for parallel lines.警告,平行线可能被零除。

I recommend some kind of K.switch like:我推荐某种K.switch如:

denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
K.switch(
    K.less(K.abs(denominator), K.epsilon()), 
    denominator + K.sign(denominator)*K.epsilon(), 
    denominator)
px /= denominator

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM