Распознавания людей, Сиамские нейронные сети, Python.

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

И опять, в этой задачи нам могут помочь нейронные сети. А если конкретней такой подход как сиамская нейронная сеть. Но давайте немного абстрагируем задачу и разберемся что же этот за подход.

Сиамская нейронная сеть.

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

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

Простой пример сети.

Первым делом нам нужна база данных для обучения. Я немного схитрю, мы не будем использовать лица ) А возьмем базу MNIST (картинки цифр) которая есть уже в библиотеке Keras . Кроме того нам нужно создать положительные и отрицательные пары. То есть например пара с одинаковыми цифрами будет положительная пара , а с разными отрицательная.

from keras.datasets import mnist
import numpy as np
# Загрузка данных 
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
# Приводим к значениям от 0 до 1
x_train /= 255
x_test /= 255

# Получаем списки индексов для каждой картинки (0...9)
digit_indices = [np.where(y_train == i)[0] for i in range(10)]
# Вызываем функцию создания пар картинок и получаем результат в переменные
tr_pairs, tr_y = create_pairs(x_train, digit_indices)
# Тестовый набор
digit_indices = [np.where(y_test == i)[0] for i in range(10)]
te_pairs, te_y = create_pairs(x_test, digit_indices)

# Трансформируем из двухмерного массива в трех мерный(28х28х1 канал)
tr_pairs = tr_pairs.reshape(108400,2,28,28,1)
te_pairs = te_pairs.reshape(17820,2,28,28,1)

np.where()

Данная функция возвращает один из двух заданных элементов в зависимости от условия. Ее можно использовать для обработки численных данных.

Пишем функцию создания пар картинок :

import random

def create_pairs(data, digit_indices):
    '''Создание положительных и отрицательных пар.
    Чередуется между положительными и отрицательными парами.
    '''
    pairs = []
    labels = []
# Получаем минимальное количество одинаковых чисел в базе
    n = min([len(digit_indices[d]) for d in range(10)]) - 1
    for d in range(10):
        for i in range(n):
            # Создаем верную пару
            z1, z2 = digit_indices[d][i], digit_indices[d][i + 1]
            pairs += [[data[z1], data[z2]]]
            # Создаем не верную пару
            inc = random.randrange(1, 10)
            dn = (d + inc) % 10
            z1, z2 = digit_indices[d][i], digit_indices[dn][i]
            # Записываем в список
            pairs += [[data[z1], data[z2]]]
            labels += [1, 0]
    return np.array(pairs), np.array(labels)

В итоге у нас получилось (108400, 2, 28, 28) пар в тренировочной базe ))))

Теперь мы создадим базовую нейро сеть которую будет возвращать нам вектор признаков :

from keras.models import Sequential
from keras.layers import Dense, Conv2D, Flatten, Input , Lambda
from keras.models import Model

def create_base_network(input_shape):
    '''Базовая сеть для совместного использования (eq. to feature extraction).
    '''
    models = Sequential()
    # Слой свертки
    models.add(Conv2D(64, kernel_size=3, activation='relu', input_shape=input_shape))
    # Слой свертки
    models.add(Conv2D(32, kernel_size=3, activation='relu'))
    models.add(Flatten())
    # Полносвязный слой
    models.add(Dense(128, activation='relu'))

    return models

Затем передадим пару изображений базовой сети, которая вернёт векторные представления, то есть векторы свойств:

input_shape = tr_pairs.shape[2:]
base_network = create_base_network(input_shape)
input_a = Input(shape=input_shape)
input_b = Input(shape=input_shape)
# Получаем вектора признаков
processed_a = base_network(input_a)
processed_b = base_network(input_b)

Input() используется для создания экземпляра тензора Кераса.

Образец
# this is a logistic regression in Keras
x = Input(shape=(32,))
y = Dense(16, activation=’softmax’)(x)
model = Model(x, y)

Давайте передадим их функции энергии для вычисления дистанции между ними. А в качестве функции энергии воспользуемся евклидовым расстоянием :

from keras import backend as K

def euclidean_distance(vects):
    x, y = vects
    sum_square = K.sum(K.square(x - y), axis=1, keepdims=True)
    return K.sqrt(K.maximum(sum_square, K.epsilon()))

def eucl_dist_output_shape(shapes):
    shape1, shape2 = shapes
    return (shape1[0], 1)

distance = Lambda(euclidean_distance, output_shape=eucl_dist_output_shape)([processed_a, processed_b])

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

  • keras.backend.sum(x, axis=None, keepdims=False) Сумма значений в Тензоре, рядом с указанной осью.
  • keras.backend.square(x) Элемент-мудрый квадрат.
  • keras.backend.maximum(x, y) Поэлементный максимум двух тензоров.
  • keras.backend.epsilon() Возвращает значение коэффициента размытия, используемого в числовых выражениях.

keras.layers.Lambda(function, output_shape=None, mask=None, arguments=None)

Обертывает произвольное выражение как Layer объект.

Тут опять с Лямбдой я не очень понял , честно скажу использовал готовый код. Если кто то может объяснить на пальцах буду признателен.

Ну а теперь соберем всю нашу модель сиамской нейро сети :

# На входе у нас две картинки , на выходе с модели дистанция выходных векторов.
model = Model([input_a, input_b], distance)
# Оптимизатор будет RMSprop
rms = RMSprop()
# Собираем модель
model.compile(loss=contrastive_loss, optimizer=rms, metrics=[accuracy])

compile(optimizer, loss=None, metrics=None )

список метрик, которые будут оцениваться моделью 
во время обучения и тестирования. 
Как правило, вы будете использовать 
metrics=['accuracy'].

Последний шаг будет написание функции ошибки для модели и метрики :

def contrastive_loss(y_true, y_pred):
    '''Контрастная потеря от Hassell-et-al.'06
    http://yann.lecun.com/exdb/publis/pdf/hadsell-chopra-lecun-06.pdf
    '''
    margin = 1
    square_pred = K.square(y_pred)
    margin_square = K.square(K.maximum(margin - y_pred, 0))
    return K.mean(y_true * square_pred + (1 - y_true) * margin_square)
def accuracy(y_true, y_pred):
    '''Вычислить точность классификации с фиксированным порогом по расстояниям.
    '''
    return K.mean(K.equal(y_true, K.cast(y_pred < 0.5, y_true.dtype)))

Все, можно запускать обучение :

model.fit([tr_pairs[:, 0], tr_pairs[:, 1]], tr_y,
          batch_size=128,
          epochs=1,
          validation_data=([te_pairs[:, 0], te_pairs[:, 1]], te_y))
# Вычислить конечную точность на обучающих и тестовых наборах
y_pred = model.predict([tr_pairs[:, 0], tr_pairs[:, 1]])
tr_acc = compute_accuracy(tr_y, y_pred)
y_pred = model.predict([te_pairs[:, 0], te_pairs[:, 1]])
te_acc = compute_accuracy(te_y, y_pred)

print('* Точность на тренировочных данных : %0.2f%%' % (100 * tr_acc))
print('* Точность на тестовых данных: %0.2f%%' % (100 * te_acc))

def compute_accuracy(y_true, y_pred):
    '''Вычислите точность классификации с фиксированным порогом на расстояниях.
    '''
    pred = y_pred.ravel() < 0.5
    return np.mean(pred == y_true)

Скачать код целиком можно с сайта.