Наконец то мы подошли к вопросу распознавания. Это уже не такая простая задача как простая классификация. Допустим у нас есть две фотографии человека и мы ходит написать программу которая будет определять, один этот же человек на фото или нет (с какой то уверенностью в этом).
И опять, в этой задачи нам могут помочь нейронные сети. А если конкретней такой подход как сиамская нейронная сеть. Но давайте немного абстрагируем задачу и разберемся что же этот за подход.
Сиамская нейронная сеть.
Допустим у нас есть два изображения лица человека(как мы их получили и т.д не важно). Что бы понять одно и тоже у нас лицо на фото, мы пропустим обе фотки через нейронную сеть. Только она не будем выдавать готовый результата ,а всего лишь вектор. То есть после каких то преобразований в сети мы получим с последнего слоя сырые данные( векторы каких то признаком, все зависит от модели сети ).
Так по чему же сиамская ? Идея в том что бы пропустить два фото через две идентичный, с одинаковыми весами, архитектурой сети(по факту через одну сеть). Получить два вектора изображений передадим их в функцию энергии, которая оценивает сходство. В качестве функции воспользуемся евклидовым расстоянием. Я не очень селен в математике, так такая большая формула , кому интересно 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)
Скачать код целиком можно с сайта.