Потеря обнаружения объекта Tensorflow YOLO - PullRequest
0 голосов
/ 22 апреля 2020

Я пытаюсь реализовать и обучить YOLO самостоятельно, основываясь на этой реализации https://github.com/allanzelener/YAD2K/. Проблемы, с которыми я сталкиваюсь, состоят в том, что значения ширины / высоты в моем тензоре предсказания взрываются, и я никогда не вижу IOU выше 0 между моими объектами предсказания и наземными объектами истины. Все это идет не так в первые несколько миниатюр первой эпохи. Потеря и большая часть моей ширины / высоты прогноза nan.

Размер моего изображения 416x416, я использую 5 якорей и имею 5 классов. Я делю изображение на сетку 13x13 для тензора прогноза [batch_size, 13, 13, 5, 10]. Основными истинными значениями для каждой партии являются [batch_size, 13, 13, 5, 5], без одного горячего для вероятностей класса.

Ниже приведена моя функция потерь (основанная на https://github.com/allanzelener/YAD2K/blob/master/yad2k/models/keras_yolo.py#L152), которая передает изображение моему модель, а затем вызывает predict_transform, который изменяет тензор и преобразует координаты.

def loss_custom(true_box_grid, x):
    # training=training is needed only if there are layers with different
    # behavior during training versus inference (e.g. Dropout).
    y_ = model(x, training=training)
    # (batch, rows, cols, anchors, vals)
    center_coords, wh_coords, obj_scores, class_probs = DetectNet.predict_transform(y_)
    detector_mask = create_mask(true_box_grid)
    total_loss = 0    

    pred_wh_half = wh_coords / 2.
    # bottom left corner
    pred_mins = center_coords - pred_wh_half
    # top right corner
    pred_maxes = center_coords + pred_wh_half

    true_xy = true_box_grid[..., 0:2]
    true_wh = true_box_grid[..., 2:4]
    true_wh_half = true_wh / 2.
    true_mins = true_xy - true_wh_half
    true_maxes = true_xy + true_wh_half

    # max bottom left corner
    intersect_mins = tf.math.maximum(pred_mins, true_mins)
    # min top right corner
    intersect_maxes = tf.math.minimum(pred_maxes, true_maxes)    
    intersect_wh = tf.math.maximum(intersect_maxes - intersect_mins, 0.)
    # product of difference between x max and x min, y max and y min
    intersect_areas = intersect_wh[..., 0] * intersect_wh[..., 1]


    pred_areas = wh_coords[..., 0] * wh_coords[..., 1]
    true_areas = true_wh[..., 0] * true_wh[..., 1]

    union_areas = pred_areas + true_areas - intersect_areas
    iou_scores = intersect_areas / union_areas

    # Best IOUs for each location.
    iou_scores = tf.expand_dims(iou_scores, 4)
    best_ious = tf.keras.backend.max(iou_scores, axis=4)  # Best IOU scores.
    best_ious = tf.expand_dims(best_ious, 4)

    # A detector has found an object if IOU > thresh for some true box.
    object_detections = tf.keras.backend.cast(best_ious > 0.6, dtype=tf.float32)

    no_obj_weights = params.noobj_loss_weight * (1 - object_detections) * (1 - detector_mask[...,:1])
    no_obj_loss = no_obj_weights * tf.math.square(obj_scores)

    # could use weight here on obj loss
    obj_conf_loss = params.obj_loss_weight * detector_mask[...,:1] * tf.math.square(1 - obj_scores)
    conf_loss = no_obj_loss + obj_conf_loss

    matching_classes = tf.cast(true_box_grid[...,4], tf.int32)
    matching_classes = tf.one_hot(matching_classes, params.num_classes)
    class_loss = detector_mask[..., :1] * tf.math.square(matching_classes - class_probs)

    # keras_yolo does a sigmoid on center_coords here but they should already be between 0 and 1 from predict_transform
    pred_boxes = tf.concat([center_coords, wh_coords], axis=-1)

    matching_boxes = true_box_grid[..., :4]
    coord_loss = params.coord_loss_weight * detector_mask[..., :1] * tf.math.square(matching_boxes - pred_boxes)

    confidence_loss_sum = tf.keras.backend.sum(conf_loss)
    classification_loss_sum = tf.keras.backend.sum(class_loss)
    coordinates_loss_sum = tf.keras.backend.sum(coord_loss)

    # not sure why .5 is here, maybe to make sure numbers don't get too large
    total_loss = 0.5 * (confidence_loss_sum + classification_loss_sum + coordinates_loss_sum)            

    return total_loss

Ниже predict_transform (на основе https://github.com/allanzelener/YAD2K/blob/master/yad2k/models/keras_yolo.py#L66), который преобразует тензор предсказания в сетка для сравнения с наземными объектами правды. Для координат центра, баллов объекта и вероятностей классов он делает сигмоид или softmax.

Для координат высоты по ширине он выполняет экспоненциальную операцию над ними (чтобы сделать их положительными) и умножает их на якоря. Кажется, именно здесь они начинают взрываться.

def predict_transform(predictions):
        predictions = tf.reshape(predictions, [-1, params.grid_height, params.grid_width, params.num_anchors, params.pred_vec_len])

        conv_dims = predictions.shape[1:3]
        conv_height_index = tf.keras.backend.arange(0, stop=conv_dims[0])
        conv_width_index = tf.keras.backend.arange(0, stop=conv_dims[1])
        conv_height_index = tf.tile(conv_height_index, [conv_dims[1]]) # (169,) tensor with 0-12 repeating
        conv_width_index = tf.tile(tf.expand_dims(conv_width_index, 0), [conv_dims[0], 1]) # (13, 13) tensor with x offset in each row
        conv_width_index = tf.keras.backend.flatten(tf.transpose(conv_width_index)) # (169,) tensor with 13 0's followed by 13 1's, etc (y offsets)
        conv_index = tf.transpose(tf.stack([conv_height_index, conv_width_index])) # (169, 2)
        conv_index = tf.reshape(conv_index, [1, conv_dims[0], conv_dims[1], 1, 2]) # y offset, x offset
        conv_dims = tf.cast(tf.reshape(conv_dims, [1, 1, 1, 1, 2]), tf.float32) # grid_height x grid_width, max dims of anchors

        # makes the center coordinate between 0 and 1, each grid cell is normalized to 1 x 1
        center_coords = tf.math.sigmoid(predictions[...,:2])
        conv_index = tf.cast(conv_index, tf.float32)
        center_coords = (center_coords + conv_index) / conv_dims

        # makes the objectness score a probability between 0 and 1
        obj_scores = tf.math.sigmoid(predictions[...,4:5])

        anchors = DetectNet.get_anchors()
        anchors = tf.reshape(anchors, [1, 1, 1, params.num_anchors, 2])
        # exp to make width and height positive then multiply by anchor dims to resize box to anchor
        # should fit close to anchor, normalizing by conv_dims should make it between 0 and approx 1
        wh_coords = (tf.math.exp(predictions[...,2:4])*anchors) / conv_dims

        # apply sigmoid to class scores to make them probabilities
        class_probs = tf.keras.activations.softmax(predictions[..., 5 : 5 + params.num_classes])

        # (batch, rows, cols, anchors, vals)
        return center_coords, wh_coords, obj_scores, class_probs

У меня есть еще одно сомнение в создании наземных данных истинности, основанных на https://github.com/allanzelener/YAD2K/blob/master/yad2k/models/keras_yolo.py#L352. Ниже box[0] и box[1] - координаты центра, i и j - координаты ячейки сетки (от 0 до 13), а box[2] и box[3] - ширина и высота.

Все они были нормализованы, чтобы быть в пределах координат сетки (от 0 до 13). Он помещает объект в сетку истинной земли с соответствующим лучшим якорем. box[0] - j и box[1] - i гарантируют, что координаты центра находятся между 0 и 1.

Однако я не понимаю np.log(box[2] / anchors[best_anchor][0]), поскольку якоря также находятся в масштабе координат сетки, и частное может быть меньше чем 1, который будет производить отрицательное число после log. Я часто вижу отрицательные значения ширины и высоты в своих наземных данных истинности во время тренировок, и не знаю, что с этим делать.

if best_iou > 0:
            adjusted_box = np.array(
                [
                    box[0] - j, # center should be between 0 and 1, like prediction will be
                    box[1] - i,
                    np.log(box[2] / anchors[best_anchor][0]), # quotient might be less than one, not sure why log is used
                    np.log(box[3] / anchors[best_anchor][1]),
                    box[4] # class label
                ],
                dtype=np.float32
            )
            true_box_grid[i, j, best_anchor] = adjusted_box

Также вот моя модель, которая очень размыта из-за моя нехватка вычислительных ресурсов.

def create_model():
    model = models.Sequential()
    model.add(Conv2D(6, 3, padding='same', data_format='channels_last', kernel_regularizer=l2(5e-4)))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.1))
    model.add(MaxPool2D())
    model.add(Conv2D(8, 3, padding='same', data_format='channels_last', kernel_regularizer=l2(5e-4)))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.1))
    model.add(MaxPool2D())

    model.add(Conv2D(12, 3, padding='same', data_format='channels_last', kernel_regularizer=l2(5e-4)))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.1))
    model.add(Conv2D(8, 1, padding='same', data_format='channels_last', kernel_regularizer=l2(5e-4)))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.1))
    model.add(Conv2D(12, 3, padding='same', data_format='channels_last', kernel_regularizer=l2(5e-4)))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.1))
    model.add(MaxPool2D())

    model.add(Flatten())
    model.add(Dense(params.grid_height * params.grid_width * params.pred_vec_len * params.num_anchors, activation='relu'))

    return model

Мне интересно, что я могу сделать, чтобы предотвратить предсказанные ширины и высоты, и, следовательно, потери от взрыва. Экспонента существует, чтобы гарантировать, что они положительны, что имеет смысл. Я мог бы также сделать для них сигмоид, но я не хочу ограничивать их значениями от 0 до 1. В документе YOLO они упоминают, что они предварительно тренируют свою сеть, так что веса слоев уже инициализируются, когда начинается обучение YOLO. , Это проблема правильной инициализации сети?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...