Привет, Хабр!

Меня зовут Паймеров Владимир, я Data Scientist и участник профессионального сообщества NTA.

Компьютерное зрение (computer vision, CV) — активно развивающаяся научная область,
связанная с анализом изображений и видео. В последнее время данному направлению
уделяется большое внимание, так как CV позволяет решать множество задач, таких как
детекцию объектов, классификацию изображений, распознавание лиц и т. д., которые
в свою очередь применяются в разных сферах жизни от мобильных приложений для
наложения масок на лицо во время звонка до построения систем безопасности,
поиска преступников и мошенников. Задумка обрабатывать изображения для
извлечения из них полезной информации возникла давно, однако возможности
техники и технологий не позволяли это делать, так как при обработке изображений
нужны большие объемы для хранения данных. Сейчас есть инструменты, позволяющие
хранить большой объем данных и обрабатывать изображения, поэтому появилось
множество инструментов для решения различных задач. Об одной из таких задач
будет рассказано в данном посте.

Навигация по материалу

Введение

Ежедневно посетители интернета оставляют на разных сайтах и в социальных сетях свои персональные данные: e‑mail, имя, телефон, возраст, фотографии. Закон 152-ФЗ запрещает собирать, хранить и обрабатывать персональные данные человека без его согласия.

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

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

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

В данном посте будет рассмотрена задача, называемая поиском похожих изображений, в которой нужно будет найти все похожие изображения из датасета на загруженную фотографию из того же датасета.

Модель поиска похожих изображений

Поиск похожих изображений — активная и быстро развивающаяся область исследований в последнее десятилетие. Исследования в данной области позволили разработать модели, которые могут помочь в работе в различных областях, например:

  • чтобы найти похожие изображения;

  • поиск фотографий‑плагиатов;

  • создание возможностей для обратных ссылок;

  • знакомство с людьми, местами и продуктами;

  • поиск товаров по фотографии;

  • обнаружение поддельных аккаунтов, поиск преступников и т. д.

Наиболее известными системами являются Google Image Search и Pinterest Visual Pin Search. Мы познакомимся с легкими и популярными подходами поиска похожих изображений, а именно:

  • применение сверточных автоэнкодеров;

  • применение предобученных моделей на основе нейронных сетей;

  • применение готовых библиотек (face_recognition).

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

CBIR при использовании глубокого обучения и поиска изображений можно назвать формой обучения без учителя:

  1. При обучении не используется никаких меток для классов;

  2. Подходы используются для преобразования изображения в векторное представление (т. е. нашего «вектора признаков» для данного изображения);

  3. Во время поиска похожих изображений, вычисляется расстояние между векторами преобразованных изображений — чем меньше расстояние, тем более релевантными / визуально похожими являются два изображения.

Загрузка, обработка и работа с данными

Для построения модели нужны данные — изображения, с которыми будет проведена работа. В целях безопасности будем использовать изображения известных личностей вместо фотографий реальных людей из паспортов. Качество входных данных проверялось вручную и при помощи инструмента ABBYY FineReader PDF15.

Работа проводилась на виртуальном окружении RAPIDS.AI CUDA 11.0.3 (cuDNN 8.0.5) TensorFlow, PyTorch Geometric с использованием графического процессора A100, ОП в 4 Гб, с 2 ядрами процессора.

Перед началом работы необходимо провести импорт библиотек и модулей из Keras и Tensorflow.

Развернуть код
import os
import keras 2.4.3
from keras.preprocessing import image
from keras.applications.imagenet_utils import decode_predictions, preprocess_input
from keras.models import Model
from tensorflow.keras import applications
import tensorflow as tf 2.3.4
from tensorflow.keras.models import save_model
import tensorflow.keras.layers as L
import numpy as np 1.18.5
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt 3.3.4
import cv2 3.4.5.20
import pandas as pd 1.1.5
import tqdm 4.62.3
from skimage import io 
import glob 
from PIL import Image, ImageEnhance, ImageChops, ImageStat, ImageDraw 8.4.0
import face_recognition 1.3.0
import fitz 1.21.1
from pathlib import Path
import shutil 2.7
import openpyxl 3.1.2
from itertools import chain 3.1

После импорта библиотек загружаем сами изображения. Для этого нужно полностью прописать путь до папки, где хранятся изображения, и создать список из путей до каждого изображения.

path ="/Users/Desktop/Python/Passports" (здесь Ваш путь до pdf-сканов документов) 
      gPDF=glob.glob('path/*.pdf')

Для того, чтобы получить изображение лица с фотографии в паспорте, pdf‑сканы необходимо перевести в формат изображения, для этого была написана следующая функция:

def extract_images_from_pdf(pdf):
    count = 0
    for tpdf in pdf:
        name = Path(tpdf).stem
        doc=fitz.open(tpdf)
        for i in range(len(doc)):
            for img in doc.get_page_images(i):
                xref=img[0]
                pix = fitz.Pixmap(doc,xref)
                if pix.n < 5:
                    pix.save(f'image_from_pdf/{name}p%s-%s.png' % (i,xref))
                else:
                    pix1 = fitz.Pixmap(fitz.csRGB, pix)
                    pix1.save(f'image_from_pdf/{name}p%s-%s.png' % (i,xref))
                    pix1 = None
                pix = None
                count+=1
    return f'Found {count} images'

  # Применение функции
extract_images_from_pdf(gPDF)

Далее получаем путь до всех обработанных изображений из pdf‑сканов и применяем функцию «face_recog_pdf» для того, чтобы вырезать область где находится лицо на фотографии. Сохраняем результат в отдельную папку.

g=glob.glob('image_from_pdf/*.png')

def face_recog_pdf(gimage):
    count = 0
    for timage in gimage:
        name = Path(timage).stem
        img = face_recognition.load_image_file(timage)
        test_loc = face_recognition.face_locations(img)
        for f in test_loc:
            top, right,bottom, left = f
            face_img = img[top:bottom,left:right]
            pil_img = Image.fromarray(face_img)
            pil_img.save(f'pdf_img/{name}_face_{count}.png')
            count+=1
    return f'Found {count} face(s) in this photos'

# Применение функции
face_recog_pdf(g)

При помощи функций extract_images_from_pdf() и face_recog_pdf(), (с использованием библиотеки OpenCV) из 20 000 pdf‑сканов паспортов было обнаружено около 10 000 паспортов с фотографиями (в сканах присутствовали изображения без фото).

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

def image2array(filelist – путь до папки с фотографиями):
    image_array = []
    for image in filelist[:200]:
        img = io.imread(image)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.resize(img, (224,224))
        image_array.append(img)
    image_array = np.array(image_array)
    image_array = image_array.reshape(image_array.shape[0], 224, 224, 3)
    image_array = image_array.astype('float32')
    image_array /= 255
    return np.array(image_array)

train_data = image2array(filelist)
print("Length of training dataset:", train_data.shape)

В результате, после выполнения функций мы получили изображения вырезанных из паспортов лиц. Следующий этап — преобразование их в вектора (для подхода с использованием сверточных автоэнкодеров функция указана выше — image2array), в последствии вектора будем использовать для сравнения и получения наборов похожих изображений.

В следующих разделах рассмотрим основные подходы для решения поставленной задачи по поиску похожих изображений.

Свёрточные автоэнкодеры для извлечения признаков из изображения

Сверточные автоэнкодеры (CAEs) — это тип сверточных нейронных сетей.

Автоэнкодер состоит из:

  • Энкодера (encoder), который преобразовывает входное изображение в представление скрытого пространства с помощью серии сверточных операций.

  • Декодер (decoder) — пытается восстановить исходное изображение из скрытого пространства с помощью серии операций свертки с повышением дискретизации / транспонирования. Также известен как деконволюция.

Подробнее о сверточных автокодерах можно прочитать здесь.

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

Применение функции summary() к модели покажет описание работы модели слой за слоем. Нужно следить за тем, чтобы размер изображения на входе соответствовал размеру изображения на выходе декодера.

Развернуть код
IMG_SHAPE = x.shape[1:]
def build_deep_autoencoder(img_shape, code_size):
    H,W,C = img_shape
    # encoder
    encoder = tf.keras.models.Sequential() # инициализация модели
    encoder.add(L.InputLayer(img_shape)) # добавление входного слоя, размер равен размеру изображения
    encoder.add(L.Conv2D(filters=32, kernel_size=(3, 3), activation='elu', padding='same'))
    encoder.add(L.MaxPooling2D(pool_size=(2, 2)))
    encoder.add(L.Conv2D(filters=64, kernel_size=(3, 3), activation='elu', padding='same'))
    encoder.add(L.MaxPooling2D(pool_size=(2, 2)))
    encoder.add(L.Conv2D(filters=128, kernel_size=(3, 3), activation='elu', padding='same'))
    encoder.add(L.MaxPooling2D(pool_size=(2, 2)))
    encoder.add(L.Conv2D(filters=256, kernel_size=(3, 3), activation='elu', padding='same'))
    encoder.add(L.MaxPooling2D(pool_size=(2, 2)))
    encoder.add(L.Flatten())
    encoder.add(L.Dense(code_size))

    # decoder
    decoder = tf.keras.models.Sequential()
    decoder.add(L.InputLayer((code_size,)))
    decoder.add(L.Dense(14*14*256))
    decoder.add(L.Reshape((14, 14, 256)))
    decoder.add(L.Conv2DTranspose(filters=128, kernel_size=(3, 3), strides=2, activation='elu', padding='same'))
    decoder.add(L.Conv2DTranspose(filters=64, kernel_size=(3, 3), strides=2, activation='elu', padding='same'))
    decoder.add(L.Conv2DTranspose(filters=32, kernel_size=(3, 3), strides=2, activation='elu', padding='same'))
    decoder.add(L.Conv2DTranspose(filters=3, kernel_size=(3, 3), strides=2, activation=None, padding='same'))
    
    return encoder, decoder


encoder, decoder = build_deep_autoencoder(IMG_SHAPE, code_size=32)
encoder.summary()
decoder.summary()

Параметры и обучение модели:

inp = L.Input(IMG_SHAPE)
code = encoder(inp)
reconstruction = decoder(code)

autoencoder = tf.keras.models.Model(inputs=inp, outputs=reconstruction)
autoencoder.compile(optimizer="adamax", loss='mse')
autoencoder.fit(x=train_data, y=train_data, epochs=10, verbose=1)

В качестве оптимизатора модель использует «adamax» (русско‑язычная документация; англо‑язычная документация), в качестве функции потерь метрику mse. Обучение проводится 10 эпох (т. е. 10 раз).

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

images = train_data
codes = encoder.predict(images) 
assert len(codes) == len(images)

Построение модели подобия изображений при помощи K-ближайших соседей (NearestNeighbours)

После получения представления сжатых данных всех изображений мы можем применить алгоритм K‑ближайших соседей для поиска похожих изображений. Он основан на расчете евклидового расстояния между векторами: те расстояния, которые будут меньше всего, будут означать, что изображения похожи.

from sklearn.neighbors import NearestNeighbors
nei_clf = NearestNeighbors(metric="euclidean")
nei_clf.fit(codes)

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

def get_similar(image, n_neighbors=5):
    assert image.ndim==3,"image must be [batch,height,width,3]"
    code = encoder.predict(image[None])    
    (distances,),(idx,) = nei_clf.kneighbors(code,n_neighbors=n_neighbors)
    return distances,images[idx]
def show_similar(image):
    distances,neighbors = get_similar(image,n_neighbors=3)
    plt.figure(figsize=[8,7])
    plt.subplot(1,4,1)
    plt.imshow(image)
    plt.title("Original image")
    
    for i in range(3):
        plt.subplot(1,4,i+2)
        plt.imshow(neighbors[i])
        plt.title("Dist=%.3f"%distances[i])
   	   plt.show()

Преимущества и недостатки использования сверточных автоэнкодеров

Преимущества:

Подход с использованием сверточных автоэнкодеров применим для обучения модели на данных, которые были выбраны для определенной задачи, если хотим «с нуля» построить свой алгоритм под конкретную задачу и нас не устраивают способы, заложенные в предобученных моделях или готовых решениях.

Недостатки:

  • модели нужна более точная настройка параметров для слоев и больше данных (которые измеряются не в тысячах, а миллионах);

  • метод затратен по времени, в отличие от применения готовых моделей и библиотек (написание кода заняло примерно 2,5 часа, когда написание кода для других подходов занимает от 15-25 минут), т.к. нужно обучать модель.

Использование предобученных моделей для извлечения признаков из изображения

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

Во‑первых, предобученные модели могли быть созданы для других целей и могут не подойти по входным параметрам или по самой конструкции нейронной сети для вашей задачи, поэтому придется её перестраивать или строить сеть самому.

Во‑вторых, предобученные модели на выходе могут получать не тот размер изображения, который нужен и при загрузке датасета и преобразовании изображений в вектор может не хватить памяти и мощности компьютера, а также это будет занимать много времени. Поэтому, если использовать предобученные модели, то нужно использовать метод понижения размерности PCA. При этом автоэнкодер понижает размерность и можно его настроить таким образом, чтобы на выходе получался вектор необходимого размера.

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

Для того, чтобы использовать предобученные модели, для начала их нужно загрузить. В качестве примера, берем модель VGG16 — сверточная сеть, с 13-ю слоями, которая была обучена на датасетах с большим количеством входных данных (14 миллионов изображений, принадлежащих к 1000 классам).

model = keras.applications.vgg16.VGG16(weights='imagenet', include_top=True)
model.summary()

Для загрузки изображений используем функцию:

def load_image(path):
    img = image.load_img(path, target_size=model.input_shape[1:3])
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    return img, x

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

feat_extractor = Model(inputs=model.input, outputs=model.get_layer("fc2").output)
feat_extractor.summary()

После того как модель построена, применяем её к нашим данным. Затем, получаем вектор признаков каждого изображения и используем метод понижения размерности PCA.

import time
tic = time.perf_counter()
features = []
for i, image_path in enumerate(filelist[:200]):
    if i % 500 == 0:
        toc = time.perf_counter()
        elap = toc-tic;
        print("analyzing image %d / %d. Time: %4.4f seconds." % (i, len(images),elap))
        tic = time.perf_counter()
    img, x = load_image(path);
    feat = feat_extractor.predict(x)[0]
    features.append(feat)
print('finished extracting features for %d images' % len(images))

from sklearn.decomposition import PCA
features = np.array(features)
pca = PCA(n_components=100)
pca.fit(features)

pca_features = pca.transform(features)

Следующий код показывает, как случайно выбирается вектор из датасета (вектор, полученный на предыдущем этапе), сравнивается расстояние от этого вектора до всех векторов в датасете, данные расстояния сортируются по возрастанию и выбираются наиболее близкие/похожие.

from scipy.spatial import distance
similar_idx = [ distance.cosine(pca_features[80], feat) for feat in pca_features ]

idx_closest = sorted(range(len(similar_idx)), key=lambda k: similar_idx[k])[1:6] # отображение первых 6 похожих изображений

thumbs = []
for idx in idx_closest:
    img = image.load_img(filelist[idx])
    img = img.resize((int(img.width * 100 / img.height), 100))
    thumbs.append(img)

# concatenate the images into a single image
concat_image = np.concatenate([np.asarray(t) for t in thumbs], axis=1)

# show the image
plt.figure(figsize = (16,12))
plt.imshow(concat_image)

Использование готовых библиотек

Поставленную задачу (поиск похожих изображений) можно также решить при помощи готовых библиотек, одной из таких является библиотека face_recognition, основанная на библиотеке dlib.

После того, как мы получили изображения с лицами, нужно перевести изображения в вектор, для этого в библиотеке face_recognition есть функция face_encodings, а для сравнения векторов, и соответственно, похожих изображений используется функция compare_faces.

Сама библиотека работает также, как и нейронные сети, т. е. был обучен датасет изображений (173 Мб в gzip‑файле), но в отличие от предыдущего способа, датасет состоял только из изображений лиц (в предыдущем способе использовались разные изображения, в т.ч. животные и транспорт).

Развернуть код
# Получаем путь до изображений с вырезанными областями с лицами
photo = glob.glob('pdf_img/*.png')

# Функция для перевода изображения в вектор
def get_vector(train_image):
    diff = {}
    bad = []
    for image in tqdm(train_image):
        try:
            img = face_recognition.load_image_file(image)
            img_enc = face_recognition.face_encodings(img)[0]
            diff.update({image:img_enc})
        except IndexError:
            bad.append(image)
    return diff, bad
# Функция для сравнения похожих изображений
def compare_faces(test_image, train_images):
    img1 = face_recognition.load_image_file(test_image)
    img1_enc = face_recognition.face_encodings(img1)[0]
    print('Original_image:')
    print(Path(test_image).stem)
    Image.fromarray(img1).show()
    print('Compared images:')
    differences = {}
    for name,vec in tqdm(train_images.items()):
        try:
            result = face_recognition.compare_faces([img1_enc], vec, tolerance=0.49)
            differences.update({name:result})
        except IndexError:
            pass            
    new_df = {key:value for key,value in differences.items() if value == [True]}
    fig = plt.figure(figsize=(15,len(new_df.keys())))
    rows,cols = 1, len(new_df.keys())
    for idx, i in enumerate(new_df.keys()):
        fig.add_subplot(rows, cols, idx+1)
        im = Image.open(i)
        print(Path(i).stem)
        plt.imshow(im)
        plt.axis(False)

# Применение функции
compare_faces(photo[9], r)

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

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

Развернуть код
# перевод изображения в вектор
def get_true_images(test_image, train_image):
    names = {}
    for t in tqdm(test_image):
        differences = {}
        try:
            img1 = face_recognition.load_image_file(t)
            img1_enc = face_recognition.face_encodings(img1)[0]
        except IndexError:
            print(t)
        for name, vector in train_image.items():
            try:
                result = face_recognition.compare_faces([img1_enc], vector, tolerance=0.4)
                differences.update({name:result})
            except IndexError:
                pass
        new_df = {key:value for key,value in differences.items() if value == [True]}
        names.update({t:list(new_df.keys())})
return names

# получение словаря со списком похожих фотографий
def get_names(dictionary):
    new_list = {}
    for idx, i in enumerate(list(dictionary.keys())):
        b = Path(i).stem
        stem = []
        for j in list(dictionary.values())[idx]:
            a = Path(j).stem
            stem.append(a)
        new_list.update({b:stem})
    data = pd.DataFrame(dict([(k,pd.Series(v)) for k,v in new_list.items()]))
    return data

# Использование функции
d = get_names(dictionary)

# Сохранение функции в Excel-файл
d.to_excel('find_faces.xlsx', sheet_name = 'Test')

Выводы

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

Для решения нашей задачи мы прошли следующие шаги:

Шаг 1: Обработали изображения, преобразовали в нужный формат.

Шаг 2: Преобразовали изображения в вектор при помощи автоэнкодера, предобученной модели или готовых библиотек.

Шаг 3: Извлеченные признаки‑вектора сравнили с набором других векторов и нашли похожие изображения на основе расстояний: чем меньше расстояния, тем более похожи изображения.

Шаг 4: Сохранили и выгрузили результаты (Excel‑файл).

Из трех рассмотренных нами подходов готовые библиотеки лучше всех отработали на точность (80–85%). Автоэнкодеры дали точность в 61%, а предобученные модели показали точность в 70%.

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

Результаты разработки алгоритмов по поиску похожих изображений пригодятся также для реализации следующих задач:

  • обнаружение поддельных документов;

  • обнаружение мошенников/подозрительных лиц (при наличии базы/ Стоп-листов и т.д.);

  • контроль при проходе в здания офисов;

  • поиск похожих изображений;

  • поиск фотографий-плагиатов;

  • обнаружение копий аккаунтов.

Весь представленный код можно найти по ссылке.

Полезные ссылки:

Ссылки на датасет

О сверточных автоэнкодерах

Про построение сверточных нейронных сетей

Библиотека face_recognition

Комментарии (17)


  1. gatoazul
    00.00.0000 00:00

    А чем сравниваете вектора? Какой индекс используете?


  1. dprotopopov
    00.00.0000 00:00

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

    Он основан на расчете евклидового расстояния между векторами

    То есть по сути алгоритм сводится к тому что

    1. берём предобученную сетку

    2. для всех изображений из банка и данного изображения берем значения из одного внутренних слоёв в режиме предсказания

    3. задаём метрику (расстояние) между изображениями как евклидово расстояние между векторами из предыдущего пункта

    4. для данного изображение ищем изображение из банка с минимальным расстоянием

    но как принять решение совпало-не совпало основываясь на значении этого расстояния?

    я решения не вижу

    критерий типа расстояние < константы явно не прокатит

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


    1. thevlad
      00.00.0000 00:00

      В общем, достаточно сравнения с порогом, либо выдачи топ 5-10 с дальнейшей обработкой.


      1. dprotopopov
        00.00.0000 00:00

        Окей...ваш алгоритм выдал дистанцию 42

        Это число многократно проверено и полностью отвечает на ваш вопрос...

        Теперь примите решение...изображения совпадают или нет

        Думаю вам будет затруднительно

        Под дальнейшей обработкой вы подразумеваете просмотр кожаным мешком?


        1. thevlad
          00.00.0000 00:00

          Порог очевидно выбирается, на основе данных для обучения, в соответствии с нужным отношением true positive/false positive. Именно исходя из этого можно определить 42 это много или мало.

          https://towardsdatascience.com/understanding-auc-roc-curve-68b2303cc9c5


          1. dprotopopov
            00.00.0000 00:00

            Не убедительно...не надо просто сыпать терминами

            Это я и без ссылок знаю, но это не тот случай

            А почему бы не рассмотреть другой алгоритм определения похожести

            Имеем много сеток которые проверяют простые признаки на фото....например...наличие-отсутствие усов, наколок, количества глаз, носов и тд

            То есть по сути по фото составляется описание...как ориентировка в полиции (даже по таким описаниям находят)...то есть каждому фото ставится вектор признаков....а далее обучаем еще одну нейросетку что два вектора признака принадлежат-не принадлежат одному лицу

            Алгоритм очевиден

            Думаю это проще и понятнее в текущих условиях использования нейросетей


            1. thevlad
              00.00.0000 00:00

              Это как раз тот случай, не знаю что уж вы там знаете, и при чем тут сыпать терминами. Почитайте любые вменяемые результаты и исследования по данному вопросу, это стандартный и полностю рабочий подход.

              В таком тоне вести диалог не вижу смысла.


              1. dprotopopov
                00.00.0000 00:00

                То есть вы не можете определить - 42 это много или мало


                1. thevlad
                  00.00.0000 00:00

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


                  1. dprotopopov
                    00.00.0000 00:00

                    Лично я не вижу почему это просто

                    Давайте несколько упростим схему нейросети до почти линейных преобразований (просто для рассмотрения данного вопроса)

                    Допустим мы на размеченных данных нашли некую величину, которая хорошо срабатывает для критерия дистанция < константа

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

                    Толда критерий дистанция < ранее вычесленная константа теряет смысл

                    И например для вскрытия биозамка надо просто осветить свое лицо светом разной яркости и колористики....вот вам и отмычка для фейс-замков


                    1. thevlad
                      00.00.0000 00:00

                      Вся суть нейросеток, что их нельзя упростить до почти линейного преобразования.

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

                      Очевидно, когда работают с изображениями делают базовую нормализацию, чтобы выровнять контраст и т.д. И так же очевидно, чтобы модель работала, обучающая и рабочая выборка, должны быть из плюс-минус одного распределения. (то есть какой-то значимый перекос в обучающих/реальных данных это априори плохо)

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


                      1. dprotopopov
                        00.00.0000 00:00

                        В том то и фишка идеи обратного распростанения ошибки для обучения, благодаря которому нейросетки заработали, что хоть чуть-чуть но пороговые функции линейны...так что вы не совсем правы

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

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

                        То есть, вы сделаете навороченный фейс-замок....я куплю пару диммируемых китайских фонариков разных спектров, подойду к замку и буду светить себе на лицо покручивая диммеры. Какая-то комбинация откроет замок.

                        Хотя можно и рожи покорчить...но это дольше по времени


                      1. thevlad
                        00.00.0000 00:00

                        Если бы все было так просто то adversarial attack, не были бы похожи на еле заметный/мутный шум. Всякие простые фокусы, приводят к тому, что точка вываливается из "аттрактора" реальных лиц и там сколько свети или не свети, все равно близкого расстояния не получится.


                      1. dprotopopov
                        00.00.0000 00:00

                        Можно не тоьько светить аналоговыми фонариками, но и проецировать на лица изображение видео...тогда каждый кадр - это попытка подбора ключа

                        Вот такой брутто-форс получается

                        А про фишки антираспознавательного макияжа тоже много пишут

                        И куча примеров, когда внесение шума заставляет нейросетку принять непрааильное решение

                        Пс

                        Вы из сбера...мои деньги точно в безопасности?


                      1. dprotopopov
                        00.00.0000 00:00

                        Вообщем опять начинаются попытки быстро найти решение...и как обычно начинают навешивать ограничения на входные данные...этого не может быть, потому что не может быть никогда...но иногда случается

                        Тут и аттрактор лиц уже привлекли и нормализацию кадра и тд и тп

                        Я не говорю что ваш алгоритм безполезен....просто он решает задачу задания метрики (отношение порядка) на наборе картинок... причем эти метрики могут быть разными в зависимости от того на что изначально обучалась нейросетка

                        Но определение порядка между обьктами не даст ответ на вопрос совпали-не совпали


  1. K_Chicago
    00.00.0000 00:00
    +2

    Скажите, только мне КПДВ видится несколько...скользкой?


    1. DaneSoul
      00.00.0000 00:00
      +1

      Туда еще просится третим кадром сцена танца в баре из комедии "Полицейской академия"-1 :-)