Примените функцию к каждому элементу трехмерного 2-канального тензора Кераса. - PullRequest
1 голос
/ 03 февраля 2020

У меня есть модель, которая обрабатывает 2 входных 3D-изображения одинакового размера, A и B, для использования в более классической функции, чтобы попытаться повысить производительность этой функции. Чтобы правильно обучить модель, мне нужно применить функцию к результату каждого прогона. Сама функция принимает 2 значения, которые соответствуют значениям в A и B с одинаковыми координатами p. Этот результат должен быть сохранен в трехмерном изображении C того же размера, что и A и B в точке p. Классические реализации этого будут выполнять for l oop по всем координатам и применять функцию для каждой пары. К сожалению, этот подход не работает для обучения модели Keras, так как выходные данные функции должны возвращаться к весам предыдущих слоев.

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

Я попытался написать собственный слой Keras для этого , Этот слой принимает 4D-тензор (channel, z, y, x) и должен возвращать тензор с формой (1, z, y, x).

В настоящее время это выглядит следующим образом:

# 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

К сожалению, этот метод серьезно замедлил обучение , В то время как модели без пользовательского слоя в конце потребовалось около 45 минут для обучения за эпоху, модель с пользовательским слоем занимает около 120 часов за эпоху.

Модель и функция сами по себе могут выполнить требуемую задачу со значительной ошибкой, однако я хотел посмотреть, смогу ли я объединить их для получения лучших результатов.


Реальное Пожизненным использованием в моем случае является разложение материалов из Dual Energy CT . Вы можете рассчитать долю материалов для каждого вокселя, предположив 3 известных материала mat1, mat2, mat3 и неизвестный образец sample.

. С помощью некоторых вычислений вы можете затем разложить этот неизвестный образец. на доли каждого известного материала f1, f2, f3 (f1 + f2 + f3 == 1,0). Нас интересует только f3, поэтому расчеты для других дробей были опущены.

Фактический код:

"""
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]

1 Ответ

1 голос
/ 03 февраля 2020

Хорошо. Первое, что очень странно, это то, что вы берете «образцы», а не «каналы».

Команда inputs[::] возвращает ровно inputs, а inputs[::][0] равно inputs[0].

Итак, вы тренируете только две выборки, независимо от размера вашей партии.

Тем не менее, все, что вам нужно, это что-то вроде:

  • Предполагая, что inputs с формой (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

Предупреждение, вы можете иметь деление на ноль для параллельных линий.

Я рекомендую какой-нибудь 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...