Внедрение функции 2D max subarray в качестве пользовательской функции потерь в Keras - PullRequest
0 голосов
/ 28 июня 2019

Я пытаюсь реализовать пользовательскую функцию потерь в Keras (бэкэнд Tensorflow).

Моя цель состоит в том, чтобы создать функцию потерь, которая принимает y_pred размера (150, 200, 1) (т. Е. Изображение 150x200 с 1 каналом), берет разницу между ним и соответствующим тензором y_true, а затем сканирует полученный результат«Разностный» массив для подмассивов всех возможных измерений, который дает сумму с максимальным абсолютным значением (двумерная задача о максимальном подмассиве).Затем функция должна вывести абсолютное значение суммы этого подмассива как убыток (число с плавающей запятой).(Я пытаюсь смоделировать эту функцию по алгоритму "MESA" из этой статьи: https://www.robots.ox.ac.uk/~vgg/publications/2010/Lempitsky10b/lempitsky10b.pdf)

Я пытался прочитать пользовательские функции потерь в Keras, и я понимаю, что нужно написатьФункция потерь в функциональном пространстве Keras. Хотя у меня в настоящее время есть оптимизированная для Cython версия моей функции потерь, я не совсем знаю, как перевести ее в дружественную для Keras версию. Код для основной основы моей функции потерь:показано ниже.

#The loss function as defined in my code
def MESA(y_true, y_pred):
    diff = y_true - y_pred
    diff = K.eval(diff)
    result = CythonMESA.MaxSubArray2D(diff)
    result = np.array([result])
    result = K.variable(result)
    return result

model.compile(
    loss=MESA,
    optimizer='adam',
    metrics=['accuracy']
)

Модуль "CythonMESA" содержит некоторые оптимизированные для Cython функции, которые я прикрепил ниже. В частности, функция "CythonMESA.MaxSubArray2D" принимает массив 2D в качестве входных данных (например, 2Dnp.ndarray object) и выводит double.

#Contents of CythonMESA.pyx

import numpy as np
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)

#a helper function that is called within the main function below
#this function computes the maximum sum subarray in a 1D array using Kadane's algorithm
cdef double KadaneAbsoluteValue(double [:] array):
    cdef int length = int(array.shape[0])
    cdef double[:] maxSums = np.zeros(length, np.float64)
    cdef double kadaneMax
    cdef int i
    for i in range(length):
        if i == 0:
            maxSums[0] = array[0]
            kadaneMax = abs(maxSums[0])
        else:
            if abs(array[i]) >= abs(array[i] + maxSums[i-1]):
                maxSums[i] = array[i]
            else:
                maxSums[i] = array[i] + maxSums[i-1]
            if abs(maxSums[i]) > kadaneMax:
                kadaneMax = abs(maxSums[i])
    return kadaneMax

#The main basis for the loss function
#Loops through a 2D array and uses the function above to compute maximum subarray
cpdef double MaxSubArray2D(double [:,:] array):
    cdef double maxSum = 0.
    cdef double currentSum
    cdef int height = int(array.shape[0])
    cdef int width = int(array.shape[1])
    cdef int i, j
    cdef double [:] tempArray
    if height >= width:
        for i in range(width):
            for j in range(i,width):
                tempArray = np.sum(array[:,i:j+1], axis=1)
                currentSum = KadaneAbsoluteValue(tempArray)
                if currentSum > maxSum:
                    maxSum = currentSum
    else:
        for i in range(height):
            for j in range(i, height):
                tempArray = np.sum(array[i:j+1,:], axis=0)
                currentSum = KadaneAbsoluteValue(tempArray)
                if currentSum > maxSum:
                    maxSum = currentSum
    return maxSum

Я на самом деле пытался скомпилировать сеть в Keras напрямую, используя вышеуказанную функцию, но, как и ожидалось, выдает ошибку.

Если бы кто-нибудь мог указать мне правильное направление относительно того, где я могу узнать, как перевести это в дружественную к Keras функцию и т. Д., Я быочень ценю это!

1 Ответ

0 голосов
/ 28 июня 2019

Простая свертка из 1 фильтра со всеми единицами, за которой следует максимальное объединение, сделает это.

subArrayX = 3
subArrayY = 3
inputChannels = 1
outputChannels = 1
convFilter = K.ones((subArrayX, subArrayY, inputChannels, outputChannels))

def local_loss(true, pred):

    diff = K.abs(true-pred) #you might also try K.square instead of abs

    localSums = K.conv2d(diff, convFilter)
    localSums = K.batch_flatten(localSums) 
        #if using more than 1 channel, you might want a different thing here

    return K.max(localSums, axis=-1)


model.compile(loss = local_loss, ....)

Для всех возможных форм:

convWeights = []
for i in range(1, maxWidth+1):
    for j in range(1, maxHeight+1):
        convWeights.append(K.ones((i,j,1,1)))

def custom_loss(true,pred):

    diff = true - pred

    #sums for each array size
    sums = [K.conv2d(diff, w) for w in convWeights]

    # I didn't understand if you want the max abs sum or abs of max sum
    # add this line depending on the answer:
    sums = [K.abs(s) for s in sums] 

    #get the max sum for each array size
    sums = [K.batch_flatten(s) for s in sums]
    sums = [K.max(s, axis=-1) for s in sums]

    #global sums for all sizes
    sums = K.stack(sums, axis=-1)
    sums = K.max(sums, axis=-1)

    return K.abs(sums)

Попытка чего-то похожего на Кадане (разделите размеры)

Давайте просто сделаем это в отдельных измерениях:

if height >= width:
    convFilters1 = [K.ones((1, i, 1, 1)) for i in range(1,width+1)]
    convFilters2 = [K.ones((i, 1, 1, 1) for i in range(1,height+1)]
    concatDim1 = 2
    concatDim2 = 1
else:
    convFilters1 = [K.ones((i, 1, 1, 1)) for i in range(1,height+1)]
    convFilters2 = [K.ones((1, i, 1, 1) for i in range(1,width+1)]
    concatDim1 = 1
    concatDim2 = 2


def custom_loss_2_step(true,pred):
    diff = true-pred #shape (samp, h, w, 1)

    sums = [K.conv2d(diff, f) for f in convFilters1] #(samp, h, var, 1) 
                                                     #(samp, var, w, 1)    
    sums = K.concatenate(sums, axis=concatDim1) #(samp, h, superW, 1)
                                                #(samp, superH, w, 1)
    sums = [K.conv2d(sums, f) for f in convFilters2] #(samp, var, superW, 1)
                                                     #(samp, superH, var, 1)
    sums = K.concatenate(sums, axis=concatDim2) #(samp, superH, superW, 1)
    sums = K.batch_flatten(sums) #(samp, allSums)

    #??? sums = K.abs(sums)
    maxSum = K.max(sums, axis-1) #(samp,)
    #??? maxSum = K.abs(maxSum)

    return maxSum
...