«Второй входной сигнал должен быть скаляром, но он имеет форму [14,16,16]» Пользовательская функция потерь tensorflow

#python #tensorflow #object-detection #loss-function #yolo

Вопрос:

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

 InvalidArgumentError:  The second input must be a scalar, but it has shape [14,16,16]
     [[{{node custom_loss_function/cond/switch_pred/_2}}]] [Op:__inference_train_function_19285]

Function call stack:
train_function
 

Я не уверен, чем это вызвано, и мой код довольно большой, но я думаю, что это связано с функцией потери(и может быть генератором данных??) поэтому я предоставлю их:

 class custom_data_generator(tf.keras.utils.Sequence):
    def __init__(self, images, boxes, new_image_size, batch_size = 8, cell_size = 16, amount_of_cells = 16, shuffle = True):
        self.boxes = boxes
        self.images = images
        self.list_IDs = list(self.images.keys())
        self.batch_size = batch_size
        self.cell_size = cell_size
        self.new_image_size = new_image_size
        self.amount_of_cells = amount_of_cells
        self.shuffle = shuffle 
        self.on_epoch_end()
    
    def __len__(self):
        return int(np.floor(len(self.images)/self.batch_size))
    
    def on_epoch_end(self):
        self.indexes = np.arange(len(self.list_IDs))
        
        if self.shuffle == True:
            np.random.shuffle(self.indexes)
            
    def __getitem__(self, index):
        #we need to slice indexes for one batch
        indexes = self.indexes[index * self.batch_size:(index   1) * self.batch_size]
        batch_indexes = [self.list_IDs[i] for i in indexes]
        X, y = self.__data_generation(batch_indexes)
​
        return X, y
​
    def __data_generation(self, batch_indexes):
        #the y_matrix is a gridcell for every picture
        #in the end we sum them all in y[] for one batch
        y_matrix = np.zeros((16, 16, 10))
        y = []
        X = []
        
        #create a dict with boxes for one batch
        batch_boxes = {}
        for index in batch_indexes:
            batch_boxes[index] = boxes[index]
        
        for one_image in batch_boxes:
            X.append(self.images[one_image])
            #iterate though all boxes in one picture
            for one_box in batch_boxes[one_image]:
                    
                    x_norm, y_norm, width_norm, height_norm = one_box[1:]
                    
                    #check which cell they belong to
                    cell_x = int(x_norm*self.amount_of_cells) 
                    cell_y = int(y_norm*self.amount_of_cells) 
                    
                    #now rescale thoose coords to the given cell
                    x_cell = self.amount_of_cells * x_norm - cell_x
                    y_cell = self.amount_of_cells * y_norm - cell_y
                    width_cell = width_norm*self.cell_size
                    height_cell = height_norm*self.cell_size
                    
                    #now append information to the y_matrix
                    #first, we check wheather this anchor box already has an object
                    if y_matrix[cell_x][cell_y][0]==0:
                        y_matrix[cell_x][cell_y][0]=1
                        y_matrix[cell_x][cell_y][1:5]=x_cell, y_cell, width_cell, height_cell
                    #maybe it does(for example, if there are two objects in one cell)
                    #so we check if there is an object in another anchor box
                    elif y_matrix[cell_x][cell_y][5]==0:
                        y_matrix[cell_x][cell_y][5]=1
                        y_matrix[cell_x][cell_y][6:10]=x_cell, y_cell, width_cell, height_cell
                    
                    else:
                        break
                    y.append(y_matrix)   
                    
        return np.array(X), np.array(y)
 

Функция пользовательских потерь:

 def custom_loss_function(y_true, y_pred):
    obj_lambda = 5
    no_obj_lambda = 0.5
    
    #(X, Y) LOSS
    
    #we check whether a bounding box is supposed to have an object in it 
    #and if it does we calculate the loss 
    
    if y_true[..., 0]==1 and y_true[..., 5]==1:
        xy_pred = tf.concat([y_pred[..., 1:3], y_pred[..., 6:8]], axis = 0)
        xy_true = tf.concat([y_pred[..., 1:3], y_pred[..., 6:8]], axis = 0)
    
    elif y_true[..., 5]==1:
        xy_pred = y_pred[..., 6:8]
        xy_true = y_pred[..., 6:8]
        
    else:
        xy_pred = y_pred[..., 1:3]
        xy_true = y_pred[..., 1:3]    
        
    xy_loss = obj_lambda*tf.keras.losses.MSE(xy_pred, xy_true)
    
    #(W, H) LOSS
    
    #because width and height are naturally bigger than coordinates the paper suggests
    #to use their squares to stabilize the loss function
    
    def sqrt_func(x):
        return tf.vectorized_map(tf.sqrt, x)
    if y_true[..., 0]==1 and y_true[..., 5] ==1:
        wh_pred = tf.concat([sqrt_func(y_pred[..., 3:5]), sqrt_func(y_pred[..., 8:10])], axis = 0)
        wh_true = tf.concat([sqrt_func(y_true[..., 3:5]), sqrt_func(y_true[..., 8:10])], axis = 0)
    
    elif y_true[..., 5]==1:
        wh_pred = sqrt_func(y_pred[..., 8:10])
        wh_true = sqrt_func(y_true[..., 8:10])
        
    else :
        wh_pred = sqrt_func(y_pred[..., 3:5])
        wh_true = sqrt_func(y_true[..., 3:5])
          
        
    wh_loss = obj_lambda*tf.keras.losses.MSE(wh_pred, wh_true)
    
    #PROBABILITY LOSS(if there is object)
    
    if y_true[..., 0]==1 and y_true[..., 5]==1:
        prob_pred = tf.concat([y_pred[..., 0:1], y_pred[..., 5:6]], axis = 0)
        prob_true = tf.concat([y_true[..., 0:1], y_true[..., 5:6]], axis = 0)
    
    elif y_true[..., 5]==1:
        prob_pred = y_pred[..., 5:6]
        prob_true = y_true[..., 5:6]
        
    else:
        prob_pred = y_pred[..., 0:1]
        prob_true = y_true[..., 0:1]
        
    #it is better to use Binary Crossentropy if we have only two options
    binary_crossentropy = tf.keras.losses.BinaryCrossentropy(
        reduction=tf.keras.losses.Reduction.SUM_OVER_BATCH_SIZE
    )
    prob_loss = binary_crossentropy(prob_pred, prob_true)
    
    #NO OBJECT LOSS
    
    if y_true[..., 0]==0 and y_true[..., 5]==0:
        noobj_pred = tf.concat([y_pred[..., 0:1], y_pred[..., 5:6]], axis =0)
        noobj_true = tf.concat([y_true[..., 0:1], y_true[..., 5:6]], axis =0)
    
    elif y_true[..., 5]==0:
        noobj_pred = y_pred[..., 5:6]
        noobj_true = y_true[..., 5:6]
        
    else:
        noobj_pred = y_pred[..., 0:1]
        noobj_true = y_true[..., 0:1]
        
    noobj_loss = no_obj_lambda*binary_crossentropy(noobj_pred, noobj_true)
    
    return xy_loss   wh_loss   prob_loss   noobj_loss
 

Заранее спасибо, любые советы были бы очень полезны

РЕДАКТИРОВАТЬ Я понял, что мне действительно нужен оператор if в моем коде, и это исправило мою текущую ошибку

Комментарии:

1. Вы не можете использовать оператор if в функции потерь. Она не поддается дифференциации. Вы можете использовать вместо этого tf.where

2. Спасибо, что удалили инструкции if, исправили эту ошибку!