Русский
Русский
English
Статистика
Реклама

Поиск

Сито для интернета интересные вещи с Shodan

19.12.2020 12:08:03 | Автор: admin


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

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



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

Поисковик получил свое название в честь вымышленного искусственного интеллекта и главного антагониста компьютерных игр System Shock и System Shock 2 SHODAN можно расшифровать как Sentient Hyper-Optimized Data Access Network, Разумная гипер-оптимизированная сеть доступа к данным.

Используя инструменты сканирования, аналогичные nmap, поисковые пауки серверов Shodan обходят и опрашивают значительную часть адресного пространства IPv4, в основном пытаясь найти каждое устройство, подключенное к Интернету, и получить его цифровой отпечаток. Сканеры Shodan определяют, какие сетевые службы предоставляет каждое найденное интернет-устройство, а также собирают все данные заголовка, которые могут идентифицировать программное обеспечение или оборудование, которые используют эти подключенные к Интернету устройства. Shodan удобно хранит всю эту информацию в своей базе данных, позволяя пользователям искать названия программ и находить любые устройства в Интернете, содержащие заданное программное обеспечение.

Shodan собирает данные в первую очередь о доступных службах веб-серверов ( HTTP / HTTPS порты 80, 8080, 443, 8443), а также FTP (порт 21), SSH (порт 22), Telnet (порт 23), SNMP (порт 161), IMAP. (порты 143 или 993), SMTP (порт 25), SIP (порт 5060), и потоковой передачи в реальном времени (протокол RTSP, порт 554). Последний может использоваться для доступа к веб-камерам и их видеопотоку.

Полученную информацию можно использовать и с пользой и во вред. К примеру, поисковик Shodan помогает ученым оценивать уровень распространения тех или иных устройств, операционных систем и веб-инструментов, а также выяснять текущий уровень проникновения интернета в любые населенные пункты с точностью до квартала. А хакеры, создавшие новый эксплойт против определенного программного обеспечения, используют Shodan как инструмент для поиска жертв в Интернете.

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

Второе важное предупреждение:
Вы не первый, кто воспользуется поиском Shodan, много опасных личностей пользовались ими до вас, а теперь они охотятся на простодушных новичков-ньюфагов. Поисковые результаты полны устройств с медовыми ловушками. Например, файлы на найденном сервере могут быть заражены троянами, а ваш компьютер может быть мгновенно атакован эксплоитом при попытке доступа к якобы уязвимому ресурсу из результатов поиска. Поэтому примите меры безопасности! Не стоит открывать внешние ссылки из результатов поиска Shodan с основного компьютера, лучше это делать в специальном образе виртуальной машины, который вы потом уничтожите. Использование прокси-серверов, VPN и файрволов также не будет лишним.

По умолчанию функция поиска сайта использует введенное слово для поиска, как точное выражение для поискового запроса. Как и в поиске гугла поисковую фразу можно уточнить специальными словами-операторами, чтобы сузить зону поиска на основе метаданных в собранных отпечатках устройств. У Шодана эти слова-операторы свои. Залогом успешного поиска в Shodan является правильно подобранные ключевые слова и операторы к ним, а вместе они образуют поисковую фразу.

Операторы Shodan


City поиск в определенном городе:
city:London

Country поиск в определенной стране. В формате кодов стран RU, US, FR.
country:fr

Hostname поиск по хосту:
Hostname: .amazon.com

Net поиск по IP-адресу:
1.1.1.1

Os поиск определенной операционной системы:
os:windows server 2012

Port поиск определенного порта:
port:443

Before/After до и после определенной даты. День/Месяц/Год:
before: 11/10/2020

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



Полный список операторов поиска Shodan можно получить на этой странице.

Страница с результатами поиска визуально содержит три колонки.



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

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

Вторая колонка показывает непосредственно сами найденные адреса. При клике по ним произойдет переход на страницу с подробными метаданными конкретного устройства.



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



Как уже было сказано выше вероятность найти в Шодане что-либо интересное прямо пропорциональна качеству поисковой фразы. Списки известных паролей по-умолчанию и отчеты фирм, специализирующихся на поиске кибер-угроз и уязвимостей, ваши лучшие друзья. Если вы не знаете с чего начать, посмотрите страницу с популярными поисковыми запросами или недавно добавленными.



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



Примеры интересных поисковых запросов


1. asus 230 country:US port:21
Устройства в США производства фирмы ASUS с функцией файлового хранилища и анонимным доступом к папкам FTP-сервера. Здесь решающим фактором оказывается указание номера порта и цифр 230, которые обозначают статус успешной авторизации



2. WIRELESS+INTERNET+CAMERA city:Moscow
Беспроводные камеры Москвы с веб-интерфейсом



3. SNC-DH160
Камеры видеонаблюдения Sony Professional Solutions, поиск по названию модели. В 2016 году был скандал в связи с обнаружением бэкдора в десятках моделей камер данного производителя. Можно ознакомиться со списком моделей и паролей по-умолчанию в этом отчете, который создала фирма по анализу киберугроз SEC Consult.



4. Webcamxp или Hikvision
Вебкамеры соответствующих систем. В некоторых случаях может потребоваться браузер Internet explorer и установка JAVA-апплета для их просмотра.



5. publicly-known credentials
Эта поисковая фраза найдет различные устройства Cisco с широко известным паролем по умолчанию, который забыли сменить. Похожий результат может дать фраза
default password



6. https://www.shodan.io/explore/category/industrial-control-systems
Просто перейдите по ссылке и выбирайте готовые варианты поиска популярных интерфейсов управления индустриальных систем. Ключевые слова на любой вкус и цвет.

Shodan не единственный поисковик по черным ходам и тайным калиткам интернета, таких поисковиков существует существует множество. Можно назвать к примеру Thingful a search engine for the Internet of Things и IoT Crawler


Если вы нашли интересные поисковые запросы, поделитесь в комментариях.



Подробнее..

Поиск данных в столбцах таблицы с пагинацией (front-часть)

26.02.2021 16:11:32 | Автор: admin

Введение

Всем добрый день. Меня зовут Александр. Сейчас я работаю в Мегафон front-end разработчиком. Проблемы поиска данных всегда отличались особенной сложностью и зачастую нестандартностью в подходах. Сегодня я бы хотел остановиться на одной интересной задаче, которую мне пришлось решать совсем недавно во время разработки платформы Интернета вещей. Впрочем, такая задача, может встретиться и на любом другом проекте, где есть динамическая подгрузка данных по REST API. Будь то подгрузка во время пагинации, или во время скроллинга, или как то иначе

Проблематика

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

Таблица с фильтрами в столбцахТаблица с фильтрами в столбцах

В данной табличке открыт фильтр по столбцу Номер БС. Простыми словами: вводя любые символы в поле ввода фильтра, вы получаете подходящие опции в дропдауне. Кликнув на любую из них, вы отфильтруете данные в таблице по этому элементу.

Каким образом можно вывести подобный дропдаун с нужными опциям?

Варианты решения

  1. Составить массив из всех элементов столбца таблицы и с помощью методов filter и includes (или им подобных) показать нужные опции в дропдауне. Проблема в том, что компонент таблицы не знает о элементах, которые находятся на других страницах. Они подгружаются только при переходе на эти страницы. А значит поиск будет осуществляться не по всем элементам.

  2. Сформировать массив как в 1-м варианте, но запрашивая его с бэка. Хороший вариант, но производительность такого подхода близится к 0. Что если записей 1 млн? В таблице именно для этого и используется пагинация.

  3. Отправлять строку фильтра на бэк, и уже с бэка получать нужные опции для дропдауна. Бэк знает обо всех записях и на нем можно использовать всевозможные алгоритмы поиска в базах данных, которые будут работать достаточно быстро.

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

Алгоритм

  1. Для каждого столбца должен существовать запрос на бэк и поле, которое мы будем брать из ответа, чтобы заполнить опции. Нужен config.

  2. После ввода данных их нужно отправлять на бэк. Можно по кнопке, но лучше использовать периодическую отправку в функции debounce.

  3. Во время процесса отправки/получения данных показывать loader

  4. Получения ответа от бэка и заполнение опций для дропдауна из заданного поля ответа

Решение

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

Итак, начнем по порядку. Как мы выяснили, для начала нужен конфиг для таблицы (для каждого столбца) и ее фильтров. Разделим его на 2 файла.

Пример конфига столбца таблицы:

{  id: 'address',  title: 'Адрес объекта',  filter: filters.address,  checked: true,  minWidth: 160}

Пример конфига фильтра:

address: {  type: 'includes',  name: 'addrFilter',  options: {    default: {      values: 'objectsList',      fetchFunc: 'fetchObjectsList',      calcFunc: 'address'    }  }}

Как можно заметить конфиги связаны по типу один-ко многим. То есть один и тот же фильтр может соответствовать нескольким столбцам таблицы.

Поле options содержит как раз поля для конфигурации опций фильтра. Например:

  • fetchFunc - имя thunk функции, которая загружает данные для этого фильтра

  • values - поле из ответа сервера, в котором содержатся опции

  • calcFunc - имя функции для трансформирования опций

Остановимся подробнее на calcFunс. При таком алгоритме, что был описан в опции попадают данные из определенного поля ответа. Но что если поле составное, например в каждую опцию фильтра должно попасть сразу несколько полей. Пример с адресом: опция фильтра = город + улица + дом

Фильтр адресаФильтр адреса

В этом нам и поможет функция предварительного обсчета опции. Вот пример реализации:

//object includes calc functionsconst calculatedData = useMemo(() => (  {    default: (values) =>    {      //default calculate    },    address: (values) =>    {      //calculate with generateAddress function, for example    },    ...}), [...]);//using this object (calcFunc from config):const data = calculatedData[calcFunc || 'default'](values)

Далее. Как уже говорилось лучше всего отправлять запросы через определенный интервал задержки. То есть пользователь вводит данные в поле, приостановился на полсекунды - произошла отправка. Реализуется это примерно так:

// debounce functionconst debounce = (fn, ms = 0) => {  let timeoutId;  return function(...args) {    clearTimeout(timeoutId);    timeoutId = setTimeout(() => fn.apply(this, args), ms);  };};//debounceFetch functionconst debounceFetch = debounce(async (func, args) =>   typeof func === 'function' && (await func(args)), 500);//sending requestuseEffect(() => {  debounceFetch(actions[fetchFunc], {     filter: { [filterName]: filterValue || null }   });}, [filter]);

Продолжаем. В текущей задаче может понадобиться определить грузится ли в данный момент какой-либо фильтр, например, чтобы показать лоадер. У нас в команде приняты некоторые стандарты для именования переменных, например, все переменные, показывающие процесс загрузки начинаются с isLoading. Например, isLoadingObjects. Поэтому идеальным решением будет создать selector для kea, который = true если хотя бы в одном стейте, содержащем в названии isLoading, содержится значение true.

Если вы используете не kea, а обычный redux или какую-то другую библиотеку - уверен, вы адаптируете этот код под свои нужды. Если нет - пишите мне. А лучше используйте kea =)

Далее вы просто показываете лоадер пока anyLoading===true, все как обычно.

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

Всю логику лучше всего будет объединить в один хук и назвать его, скажем, useFiltersOptions.

Выводы

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

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

Подробнее..

Перевод Как работает поиск изображений в Dropbox

27.05.2021 20:23:24 | Автор: admin

Если вам нужно найти фотографию, сделанную на пикнике несколько лет назад, вряд ли вы помните имя, которое камера автоматически присвоила файлу в момент съёмки, например, 2017-07-04 12.37.54.jpg.Вы просматриваете всё подряд фотографии, их эскизы, пытаетесь определить объекты или признаки искомого и не важно, ищете ли вы потерянное фото или хотите подыскать в архивах приличный снимок для презентации нового проекта.

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

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


Результаты поиска изображений по ключевому слову "пикник"Результаты поиска изображений по ключевому слову "пикник"

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

Наш метод

Формулируем задачу поиска изображений: необходимо найти функцию релевантности, которая принимала бы на входе (текстовый) запрос q и изображение j, а на выходе выдавала бы оценку релевантности s в баллах, показывающую, насколько точно изображение соответствует запросу:

s = f(q, j).

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

Классификация изображений

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

Категории могут быть следующими:

  • конкретные объекты на изображении, например, дерево или человек;

  • общие дескрипторы сцены, например, на улице или свадьба;

  • характеристики самого изображения, например, чёрно-белое или крупный план.

За последнее десятилетие был сделан огромный шаг вперёд в области в классификации изображений с помощью свёрточных нейронных сетей достаточно упомянуть замечательную работу 2012 года. Krizhevsky и др. на турнире ImageNet Сhallenge. С тех пор, благодаря усовершенствованию архитектуры моделей, улучшению методов обучения, большим базам данных, например Open Images или ImageNet, и простым в использовании библиотекам, таким как TensorFlow и PyTorch, исследователи создали классификаторы изображений, способные распознавать тысячи категорий. Оцените, насколько качественно сегодня работает классификация изображений:

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

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

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

Векторы слов

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

Получение векторных представлений текста предмет большого количества исследований в области обработки с использованием естественного языка. Один из наиболее известных методов в этой области word2vec описан в статье Mikolov и др. в 2013 году. Каждому слову в словаре функция Word2vec присваивает собственный вектор, и, исходя из этого, слова имеющие сходные значения, будут иметь близкие друг к другу векторы. Эти векторы находятся в d-мерном векторном пространстве слов, где d обычно составляет несколько сотен.

Узнать векторное представление поискового запроса можно, просто просмотрев его вектор word2vec. Этот вектор не является частью пространства категорий векторов классификатора изображений, однако можно преобразовать его в пространство категорий, ссылаясь на названия категорий изображений следующим образом:

  1. Для слова запроса q можно получить d-мерный вектор слов qw, нормализованный к единичному вектору. Для векторов в пространстве слов будем использовать нижний индекс w, а для векторов в пространстве категорий нижний индекс c.

  2. Для каждой категории получаем нормализованный вектор слов для названия категории ciw. Затем определяем значение mi = qw - ciw косинусное сходство векторов запроса и i-й категории. Полученная оценка от -1 до 1 показывает, насколько близко слово запроса соответствует названию категории. Сводя отрицательные значения к нулю (то есть применяя функцию mi = max(0, mi)), получаем оценку в том же диапазоне, что и результаты классификатора изображений.

  3. Таким образом, можно вычислить qc = [m1 m2 ... mC], вектор в C-мерном пространстве категорий, показывающий, насколько близко запрос соответствует каждой из категорий аналогично тому, как вектор классификатора изображений для каждого изображения показывает, насколько близко изображение соответствует каждой из категорий.

Шаг 3 осуществляем обычное векторно-матричное умножение, qc = qwC, где C матрица со столбцами векторов слов категории ciw.

После сопоставления запроса с вектором пространства категорий qc к вектору пространства категорий для каждого изображения можно применить косинусный коэффициент и получить таким образом окончательный балл релевантности для изображения s = qcjc.

Мы получили функцию релевантности. С её помощью мы ранжируем изображения побалльно и формируем список результатов запроса. Применение данной функции к набору изображений можно также выразить как векторно-матричное умножение, s = qcJ, в котором каждый столбец J представляет собой выходной вектор классификатора jc для изображения, а s вектор балльных оценок релевантности для всех изображений.

Пример

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

Предположим, пользователь искал берег. Просматриваем вектор слов, получаем [0,350,62 0,70], затем умножаем на матрицу векторов слов категорий и проецируем запрос на пространство категорий.

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

Проекция вектора слов запроса на пространство категорийПроекция вектора слов запроса на пространство категорий

Описание модели

Наш классификатор изображений представляет собой сеть EfficientNet, натренированную на наборе данных OpenImages. Классификатор выдаёт балльные оценки примерно по 8500 категориям. Мы выяснили, что данная архитектура и набор данных обеспечивают хорошую точность при разумных затратах. Это довольно важно, если приходится работать с клиентской базой Dropbox.

Для тренировки и запуска модели будем использовать TensorFlow и заранее обученные векторы слов ConceptNet Numberbatch. Полученные результаты оказались удовлетворительными, и, что важно для нас, модель способна работать с несколькими языками, возвращая сходные векторы для слов на разных языках со сходными значениями. Это упрощает процесс поиска изображений на разных языках: векторы слов dog на английском языке и chien на французском сходны, поэтому мы можем осуществлять поиск на обоих языках без перевода поискового запроса с одного языка на другой.

Для поиска по нескольким словам будем анализировать запрос как логическую операцию AND, применённую к отдельным словам. Мы также ведём перечень терминов, состоящих из нескольких слов, например beach ball, которые можно рассматривать как одно слово. Если запрос содержит один из таких терминов, мы делаем альтернативный синтаксический разбор и применяем операцию OR для двух разобранных запросов, после чего запрос beach ball принимает вид (beach AND ball) OR (beach ball). Этот термин будет подходить для поиска как больших разноцветных надувных мячей для игры на пляже, так и для теннисных мячей на песке.

Архитектура в производственной среде

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

Поэтому вместо создания экземпляра J для каждого запроса мы используем описанный выше приближённый метод, который может быть эффективно реализован на поисковом движке Dropbox Nautilus.

По сути, Nautilus состоит из прямого индекса (forward index), сопоставляющего каждый файл с определёнными метаданными (например, именем файла) и полным текстом файла, и инвертированного индекса (inverted index), сопоставляющего каждое слово со списком публикаций (posting list) для всех файлов, содержащих это слово. При текстовом поиске содержимое индекса для нескольких файлов с рецептами может выглядеть примерно так:

Содержание поискового индекса для текстового поискаСодержание поискового индекса для текстового поиска

Если пользователь ищет белое вино, мы ищем эти два слова в инвертированном индексе и обнаруживаем в doc_1 и doc_2 оба слова, поэтому мы должны включить их в результаты поиска. В doc_3 есть только одно из этих слов, поэтому мы должны либо опустить его, либо поставить последним в списке результатов.

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

Применение методов текстового поиска для поиска изображений

Систему текстового поиска можно использовать для реализации нашего алгоритма поиска изображений. В прямом индексе можно хранить вектор пространства категорий jc каждого изображения. В инвертированном индексе для каждой категории будет храниться список публикаций изображений с положительными балльными оценками для данной категории.

Содержание поискового индекса для поиска изображений по содержимомуСодержание поискового индекса для поиска изображений по содержимому

Предположим, пользователь ищет слово пикник:

  1. Находим вектор qw для слова пикник и умножаем его на матрицу проекции пространства категорий C и получаем qc, как описано выше. C это фиксированная матрица, одинаковая для всех пользователей, поэтому её можно хранить в памяти.

  2. Для каждой категории, имеющей ненулевую запись в qc, получаем список идентификаторов из инвертированного индекса. Объединение таких списков представляет собой набор совпадающих изображений, полученных в результате поиска, однако эти результаты ещё необходимо ранжировать.

  3. Для каждого результата поиска возьмём вектор пространства категорий jc из прямого индекса и умножим его на qc, чтобы получить балльную оценку релевантности s. Получаем результаты с балльной оценкой выше порогового значения, ранжированные по этой балльной оценке.

Оптимизация для масштабируемости

Описанный подход по-прежнему связан со значительными затратами как с точки зрения пространства для хранения данных, так и с точки зрения времени обработки запросов. Для 10000 категорий в прямом индексе для каждого изображения необходимо хранить 10000 балльных оценок классификатора, что при использовании четырёхбайтовых значений с плавающей запятой составляет 40 КБ. А поскольку оценки классификатора редко когда бывают строго нулевыми, в большинство из таких 10000 списков идентификаторов будет добавлено стандартное изображение. Если для идентификаторов изображений использовать четырёхбайтовые целые числа, добавятся ещё 40 КБ и стоимость индексирования одного изображения составит 80 КБ. Другими словами, для многих изображений их индекс будет больше по размеру, чем сам файл изображения!

Что касается времени обработки запроса, под которым понимается задержка, с которой система выдаёт пользователю результаты поиска, мы можем ожидать, что около половины балльных оценок совпадения запроса и категории mi будут положительными, поэтому нам придётся прочитать около 5000 списков идентификаторов из инвертированного индекса. Если сравнивать с текстовыми запросами, то они обычно считывают всего около 10 списков публикаций.

К счастью, существует множество близких к нулю значений, которые вполне можно отбросить, и это не повлияет качество поиска. Оценка релевантности для каждого изображения была представлена выше как s = qcjc, где qc балльная оценка соответствия между запросом и каждой из примерно 10000 категорий, а jc балльные оценки приблизительно 10000 категорий, полученные с помощью классификатора. Оба вектора в основном состоят из близких к нулю значений, которые не вносят практически никакого вклада в s.

В нашем приближении мы определим значения всех записей qc и jcкак равные нулю, кроме нескольких самых больших. Экспериментально было выяснено, что сохранение 10 записей в qc и 50 записей в jc вполне достаточно для получения не менее содержательного итогового результата. Экономия на хранении и обработке данных получилась довольно существенной:

  • В прямом индексе вместо 10000-мерных плотных векторов мы храним разрежённые векторы с 50 ненулевыми записями, то есть с 50 записями с наиболее высокими балльными оценками категорий для каждого изображения. В разрежённом представлении мы храним позицию и значение каждой ненулевой записи; 50 двухбайтовых позиций (записанных целыми числами) и 50 четырёхбайтовых значений (записанных в виде числа с плавающей точкой) требуют всего около 300 байт.

  • В инвертированном индексе каждое изображение добавляется не в 10000, а в 50 списков идентификаторов, что обходится всего в 200 байт. Таким образом, общий размер индекса для каждого изображения составляет 500 байт вместо 80КБ.

  • Во время запроса qc имеет 10 ненулевых записей, поэтому нужно просканировать только 10 списков идентификаторов примерно такой же объём работы выполняется в текстовых запросах. Набор результатов сужается, и такой набор можно быстрее оценить.

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

Доступность функций

Функция поиска в изображениях в настоящее время доступна для всех наших пользователей профессионального и бизнес-уровня. Объединяя функции поиска в любых изображениях, OCR-поиска в изображениях и документах и полнотекстового поиска в текстовых документах, мы делаем большинство файлов пользователей доступными для поиска на основе содержимого.

Поиск в видео?

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

Искусственный интеллект сегодня экономит не только время, но и силы, которые направляются на решение всё более сложных задач. И если вам интересно решать задачи в области искусственного интеллекта, вы можете обратить внимание на наш курс "Machine Learning и Deep Learning", разработанный совместно с компанией NVIDIA.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Как не держать лишнее железо и справляться с ростом нагрузки внедрение graceful degradation в Яндекс.Маркете

14.01.2021 12:05:51 | Автор: admin

Привет, меня зовут Евгений. Я разрабатываю инфраструктуру поиска Яндекс.Маркета. Хочу рассказать, как graceful degradation помогает нам обрабатывать больше запросов, чем физически могут выдержать наши сервера, и что происходит с поиском в Маркете, если один из дата-центров отключается.

Проблема

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

Обычное состояниеОбычное состояние

Но когда все ДЦ работают, каждый из них оказывается задействован лишь на 60%. Ещё 10% резервируются для экспериментов и непредвиденного роста, а оставшиеся 30% используются только в случае, если один из ДЦ отключается, и нужно перераспределить запросы.

ДЦ 2 отключёнДЦ 2 отключён

Если сервис работает на сотнях серверов, 30% это огромное количество железа. А поскольку отключаются ДЦ очень редко, резервные мощности почти всегда простаивают.

Нам хотелось бы меньше греть воздух, ведь если сократить количество железа в каждом ДЦ хотя бы на 10%, речь уже шла бы о внушительных суммах. При этом, если просто вытащить часть железа, а затем при пиковой нагрузке какой-то из ДЦ отключится, оставшиеся мощности могут не справиться. Резко вырастет время ответа пользователю, а некоторые запросы будут вообще оставаться без ответа.

ДЦ 1 и ДЦ 3 не справляются с нагрузкойДЦ 1 и ДЦ 3 не справляются с нагрузкой

Применяем graceful degradation

Graceful degradation или изящная деградация это подход к построению систем, при котором система продолжает выполнять свою основную функцию, даже если отдельные её элементы вышли из строя.

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

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

Чтобы запустить graceful degradation, нам надо было решить две задачи:

  1. Разработать механизм уменьшения нагрузки.

  2. Сделать автоматизацию включения механизма.

Механизм уменьшения нагрузки

Уменьшить нагрузку можно по-разному. Один из вариантов отвечать ошибкой на некоторые запросы. Но тогда надо понять, на что можно так отвечать, а на что нет. То есть нужен какой-то параметр важность, которого мы не знаем. Если спросить у наших клиентов, что важно, они ответят, что важны все запросы, и ничего нельзя отбрасывать. Поэтому этот путь нам не подошёл.

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

Таким образом, мы выбрали третий вариант снижать качество. Основное преимущество этого подхода возможность уменьшать нагрузку на 5%, 10% и так далее.

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

Бэкенд получает запрос и раcпределяет его на 8 серверов с шардами. В шардах хранятся предложения от магазинов.

Общая схема обработки поискового запросаОбщая схема обработки поискового запроса

На каждом шарде поиск проходит несколько стадий. На стадии фильтрации ищется примерно 50000 предложений, это число зависит от категории. На этапе ранжирования для каждого предложения вычисляется релевантность, учитывается цена, рейтинг товара, рейтинг магазина и ещё более 2000 факторов. ML по факторам вычисляет вес каждого предложения. Затем берётся только 48 лучших. Meta Search получает эти 48 предложений с каждого шарда, то есть всего 48*8=384 предложения. Предложения снова ранжируются, опять берётся 48 лучших. Последние 48 уже показываются пользователю. То есть чтобы показать нашим пользователям 48 телефонов, мы обрабатываем 400 000 предложений.

Количество обрабатываемых документов без graceful degradationКоличество обрабатываемых документов без graceful degradation

В случае с graceful degradation, когда надо уменьшить нагрузку, мы можем скомандовать: теперь обрабатывай 95% документов, а теперь 90% или 80%. Если обрабатывать 95%, то есть 400000*0.95=380 000 документов, то из них всё равно выбираются 48 лучших предложений для выдачи. И в среднем только 2 предложения будут отличаться от изначальной выдачи без снижения качества. При таком маленьком изменении большинство пользователей даже не заметят разницы.

Количество обрабатываемых документов при включении механизма уменьшения нагрузкиКоличество обрабатываемых документов при включении механизма уменьшения нагрузки

Автоматизация включения механизма

Автоматизация работает за счёт постоянного мониторинга загрузки CPU. Если нагрузка становится выше 90%, автоматика начинает снижать качество. На 90% снижение небольшое, но если нагрузка продолжает расти, процент деградации повышается линейно и доходит до максимума при 100% загрузки CPU. Такой подход позволяет снижать качество минимально.

Общий алгоритм выглядит так:

При выключении ДЦ: балансеры перераспределяют запросы в оставшиеся ДЦ => нагрузка на CPU повышается => при превышении порогового значения происходит снижение качества по заданной формуле.

При включении ДЦ: балансеры перераспределяют запросы на все ДЦ => нагрузка на CPU снижается => понижение качества прекращается.

Повышение нагрузки при выключении ДЦ. Линии на верхнем графике показывают загрузку CPU в отдельных ДЦ. Нагрузка выросла с 82% до 98%. Нижний график показывает процент срезанных документов. Повышение нагрузки при выключении ДЦ. Линии на верхнем графике показывают загрузку CPU в отдельных ДЦ. Нагрузка выросла с 82% до 98%. Нижний график показывает процент срезанных документов.

Внедрение

Для внедрения мы провели несколько экспериментов и понижали качество на небольшом проценте пользователей. В течение недели мы постоянно отслеживали уменьшение нагрузки и одновременно количество переходов на магазины-партнёры. Имея на руках цифры, мы договорились с бизнесом на максимальное снижение кликов в 1% при снижении качества. Исходя из этого мы посчитали, сколько серверов можем отдать другим проектам или выключить для резерва. У нас получилось 15% всех мощностей поиска.

В конце, когда всё было готово, мы провели эксперимент с отключением дата-центра, чтобы убедиться, что всё работает как надо.

Выводы

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

Подробнее..

Можно ли улучшить поиск сложных товаров в интернет-магазинах (или Яндекс Маркете)?

17.01.2021 10:16:16 | Автор: admin

Возможно, те кто постарше, еще помнят price.ru. Вот и я еще помню те времена, когда Яндекс Маркета не было вообще. И даже был период, когда я пытался делать аналог price.ru для американского заказчика, и при этом amazon.com был у нас всего-лишь одним из многих сайтов - источников сведений о товарах (книгах, CD-ROM и т.п.), при том не самым крупным, и еще был живой yahoo... В общем, с тех пор многое изменилось.

Потом появился Яндекс Маркет, сильно вырос Амазон, и скажу прямо - много лет я использовал их как основной инструмент для поиска нужных мне товаров. А теперь вот подумываю, не уйти ли куда-то, на e-katalog скажем (шутка, там свои проблемы)? Или найти другое решение, получше?

Почему, спросите вы? Ну, в первую очередь потому, что поиск. Поиск категорически перестал меня устраивать.

Попробуем описать для начала, с какими проблемами я сталкивался при поиске. Яндекс Маркет тут будет как пример, но многое из описанного касается и других похожих сайтов (Амазон - первый же кто приходит на ум, и там все почти так же плохо).

Как выглядит поиск здорового человека? Ну, как-то вот так наверное :)

Если что, это реальный скришнот с мобильного приложения Маркета, сделанный с год назад. Сочетание страницы про IPhone и фото циркулярки от DeWalt сделало тогда мой день. Ну ок, это был баг, его исправили, так что забудем про это и вернемся к реальному поиску и его результатам.

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

Начнем с того, какие на мой взгляд, бывают интернет-магазины?

  • Узко специализированные. В нашем случае это например магазин Рубанков, инструменты и прочие товары для столярки. Его отличает от прочих то, что там знают о существовании компаний-производителей японского ручного инструмента Silky, или известнейшей канадской инструментальной компании Veritas. И продукция этих производителей не просто есть на сайте, но и, как правило, в наличии. Специализация не значит, что тут вы найдете все - скажем, инструмент элитного уровня от производителя Bridge City вы вообще в РФ скорее всего не купите. Но тем не менее, тут есть многое.

  • Просто специализированные. Это например Все Инструменты. Тут вы тоже сможете найти товары от той же Silky - но в наличии только одна пилка (из обширного ассортимента в десятки моделей).

  • Не специализированные. Ну это просто Яндекс Маркет, и большинство тех, кто продает на нем. Магазины из двух предыдущих пунктов тут тоже представлены, но структура каталога - от Маркета. Так вот, в нашем конкретном случае Маркет вообще не знает о существовании таких производителей, как Silky или Veritas, и даже если товар (скажем пилу) вы сможете найти - то не через каталог и выбор производителя. А поиск по Veritas приведет вас в лучшем случае к швейным машинкам.

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

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

Во-вторых, типовая, уже скорее техническая проблема - это отсутствие уникального идентификатора у товара. Очень часто один товар продается в разных комплектациях. Компьютер в памятью 8 или 16 гигабайт из коробки, инструмент в картонной коробке или кейсе, с теми или иными дополнительными приспособлениями либо расходкой. При этом в сущности, это одна и та же модель - но стоимость дополнительной расходки, кейсов, памяти и пр. вполне может составлять значительную часть цены. Эта проблема приводит к дублированию позиций в поиске, и затрудняет выбор лучшего предложения.

Ну и наверное третья - небольшая глубина и ширина каталога в специализированных разделах. Скажем, при том что в Яндекс Маркете есть 14 (sic!) типов ручных рубанков - там все равно нет например грунтубелей. Ну т.е. даже если у продавца такой появится - в поиске он все равно будет валяться в общей куче (то, что в Яндекс Маркете при этом в основном вообще одна китайская дешевка - это отдельная история, тут я бы винил скорее покупателей, что лучше берут - то и в продаже).

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

Что мы тут видим? Яндекс Маркет знает, что был вот такой товар, Bosch Biturbo GKT 18V-52 GC бла-бла-бла (в конце концов, я же как-то его в закладки поместил, правда?). И он раньше продавался, а теперь (в этом магазине) не продается. Но он был, и Маркет знает про него достаточно, чтобы его найти. В теории... а попробуем найти реально?

Я не буду приводить вам полный скриншот результатов поиска, они длинные.

Что мы тут видим? Мы видим, что именно этого товара в списке результатов нет. А есть куча других, которые сюда попали не иначе, как по недоразумению. Ну то есть, с одной стороны - в списке куча пил, в названии которых встречается 18V или 18В. С другой - вот на третьей позиции пилка от Bosch на 12 вольт. С третьей - есть пилы сетевые. Есть Bosch PRO и Bosch DIY (несовместимые друг с другом). Есть вообще другие производители. Но самое смешное то, что в длиннющем списке есть лишь одна сетевая погружная пила, которая реально является аналогом искомого товара - на восьмом месте в списке. Ну т.е. на 99% результаты поиска - мусор.

Если вы вдруг не заметили: оно ищет по 18V. А по слову the в английском тексте они не пробовали искать? Нашли бы намного больше :) Где же ваш ИИ, в конце концов?

Решение?

Главный вопрос - что с этим можно сделать? Если бы директором был я (с), то я бы пошел по пути, который давно опробован (скажем, в картах), и пусть не идеально, но работает. Пусть у вас (а в данном случае у вас - это ни у кого) нет специалистов, которые глубоко разбирались бы в товарах, зато у вас есть сообщество. И это сообщество как раз разбирается - ну хотя бы настолько, насколько ему это нужно. Следует сделать так, чтобы каталог товаров был устроен наподобие wiki. И дать возможность пользователям самим расширять и исправлять характеристики товаров.

Сейчас некоторые карточки товаров уже отличаются от других. Вот конкретно эта - предложение магазина itUnite:

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

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

Одна из вещей, которые сообщество могло бы сделать - это как раз убрать это дублирование, т.е. отметить в каталоге, что вот эти карточки - это комплектации или предложения одного и того же товара. Вторая - это занести в каталог такие малоизвестные :) фирмы, как канадская Veritas, или Lee Nielsen, научить софт различать японские пилы катаба и дозуки, и учитывать многие другие тонкости. По сути - пополнять и исправлять каталог на постоянной основе.

Вернемся еще раз к нашей Bosch Biturbo GKT 18V-52 GC. Почему сегодня результаты поиска нерелевантны? Потому что они очевидно не учитывают один из признаков этой пилы - она погружная. Т.е. мы можем нажать на рукоятку, и погрузить диск в материал. Все пилки с префиксом GKS этого не умеют, так что аналогом не являются. С другой стороны, есть ли у найденных товаров что-то общее? Да, есть - у некоторых. Буковка G в обозначении пилы показывает, что пила умеет работать по направляющей шине. Т.е. GKT 18V-52 GC умеет, и GKS 18V-57 G тоже. И как ни странно, но умеет и GKS 12V-26 - хотя в обозначении этого признака в виде буковки тут G нет. Что-то из атрибутов можно было бы извлечь, применяя методы типа машинного обучения, NLP и т.п. Но проще всего - доверить это сообществу.

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

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

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

Что еще можно и нужно улучшить?

Стоит заметить, что редактировать сообществу нужно позволять не только атрибуты, но и метаданные о них (тип данных, по сути), потому что сегодня органы управления поиском - это отдельная боль. Скажем, как выглядит чекбокс курильщика? А вот он:

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

И это я еще на начал вспоминать о том, то бывают инструменты, умеющие работать как от сети, так и от аккумулятора. Скажем, из того что сразу пришло в голову - пылесос DeWALT DCV582.

Замечу так же, что если в основу разметки атрибутов положить что-то типа RDF, и некоторые атрибуты сделать URI, то можно было бы легко делать связи между товарами, скажем, принтером, и его расходниками, лобзиком и пилками для него, ну и так далее. Причем связи типизированные, не просто "см. также", а именно для этого инструмента подходят вот такие аккумуляторы.

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

Выводы

Для начала, хочу отметить, что все что выше описано, не является проблемами, специфичными именно для Яндекс Маркета. Скажем, поиск Амазона в аналогичных ситуациях ведет себя ничуть не лучше, и знание Амазона о производителях тоже оставляет желать. Что с упомянутым e-katalog? Ну тут ситуация чуть другая, судя по всему (я вижу лишь то, что я вижу снаружи) тут за сайтом стоит именно и только пополняемый каким-то способом каталог моделей, поэтому части проблем либо нет, либо они иного сорта. Реально он меня больше устраивает в ряде специфических ситуаций, когда нужно найти не "что-то вроде вот этого", а "конкретно вот это, и если можно - подешевле". Но ассортимент, а в итоге объем и глубина каталога тут явно меньше, поэтому шансы найти что-то редкое вообще стремятся к нулю.

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

Подробнее..

Найди меня, если сможешь

30.03.2021 20:09:12 | Автор: admin

Когда мы начали разрабатывать компонент Combobox для нашей библиотеки, мы столкнулись с вопросами, как правильно должен работать поиск:

  1. Нужно ли выделять совпадения в выпадающем списке?

  2. Совпадения должны быть по первым буквам или нет?

  3. Если нет, нужно ли показывать выше то, что совпадает по первым буквам?

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

Характеристики поиска

Место поиска

Поиск может находиться на странице, в модалке или в попапе.

Пример поиска на странице в Яндекс.ДиректеПример поиска на странице в Яндекс.Директе

Видим, по чему ищем

  • сразу видим,

  • не видим.

Самый известный пример поиска, когда не видим, по чему будем искатьСамый известный пример поиска, когда не видим, по чему будем искать

Начало поиска

  1. По запросу поиск начнется только после нажатия на кнопку или Enter на клавиатуре.

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

Пример поиска на лету по справочнику городов и отелей в АвиасейлсПример поиска на лету по справочнику городов и отелей в Авиасейлс

Соответствие

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

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

Частичное и частичное по первым символамЧастичное и частичное по первым символам

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

Полное соответствиеПолное соответствие

Сортировка

  1. Стандартная варианты в результате поиска сохраняют свою последовательность в сортировке.

  2. По релевантности в этом случае сортировка меняется и выше показываются те варианты, которые наиболее близки к поисковому запросу.

Стандартная сортировка и по релевантностиСтандартная сортировка и по релевантности

Выделение совпадений

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

Без выделения и с выделениемБез выделения и с выделением

Комбинируем эти характеристики для разных кейсов в нашем интерфейсе

1. По странице

Место поиска: на странице

Видим, по чему ищем:сразу видим

Начало поиска:на лету

Соответствие:частичное

Сортировка:стандартная

Совпадения:без выделения

Используем, когда нужно найти что-то на конкретной странице сайта.

2. По всему сайту

Место поиска: в модалке

Видим, по чему ищем:не видим

Начало поиска:по запросу

Соответствие:частичное

Сортировка:по релевантности

Совпадения:с выделением

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

3. В справочнике по тексту

Место поиска: в попапе

Видим, по чему ищем:сразу видим / не видим

Начало поиска:на лету

Соответствие:частичное по первым символам

Сортировка:стандартная

Совпадения:с выделением

Используется в компонентах, например, в ComboBox. В нашей дизайн-системе выпадающий список является попапом, т.к. он не блокирует работу со страницей. Если значение в выпадающем списке состоит из нескольких слов, то проверяется на совпадение не только самое первое слово, проверка на совпадение идет через следующие разделители: пробел, точка, дефис. Пример: Urban Gorn; urban.gorn@mail.ru; Urban-Gorn. Во всех трех случаях поиск по слову "Gorn" сработает.

4. В справочнике по цифрам

Место поиска: в попапе

Видим, по чему ищем:сразу видим / не видим

Начало поиска:на лету

Соответствие:частичное

Сортировка:по релевантности

Совпадения:с выделением

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

Поиск фильтрация по названию

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

На странице рекомендуется использовать что-то одно, в зависимости от того, какие задачи решает пользователь.


СпасибоСергею Токареву за помощь в подготовке материала.

Подробнее..

Перевод У каждого приложения должна быть палитра команд

20.05.2021 12:07:50 | Автор: admin
В старых и новых приложениях незаметно начинает появляться инструмент, упрощающий взаимодействие и ускоряющий выполнение действий. Это мощное поле поиска, которое я называю power bar; иногда оно имеет название command palette.

Power bar, похожая на поиск Spotlight в macOS, встраивается в приложение и обычно вызывается сочетанием горячих клавиш CMD+K (или CMD+SHIFT+P). После её вызова пользователь вводит в неё то действие, которое хочет выполнить. Однако в отличие от Spotlight, power bar позволяет выполнять задачи, а не просто искать файлы или переходить в другие части приложения.


Command palette приложения Superhuman.

В таких приложениях, как, например, в клиенте электронной почты Superhuman, power bar позволяет выполнять всё, что можно сделать нажатием на кнопку, не убирая пальцев с клавиатуры. Нажать сочетание горячих клавиш в письме внутри Superhuman, ввести schedule (запланировать), нажать на Enter и ввести next week (следующая неделя) гораздо быстрее, чем искать и нажимать кнопки в интерфейсе, если вы знаете, как пользоваться этой возможностью.

Правильно спроектированная power bar позволяет пользователям перемещаться по всему приложению, не прикасаясь к мыши, а для нахождения задачи она не требует точного совпадения поисковой фразы. При поиске snooze (перенести) или later (позже) вместо schedule (запланировать) должны выдаваться те же результаты, потому что люди используют для одной задачи разные слова, особенно когда ещё не освоили приложение. Самое важное, чтобы в результатах поиска power bar отображались практически все задачи, которые можно выполнить в приложении; таким образом она будет полезна всегда, а не только в отдельных случаях.


Power bar в приложении Visual Studio Code.

В последние годы power bar всё чаще начали появляться во всевозможных приложениях. Я находил их и в инструменте для создания слайд-шоу Pitch, и в календаре Cron, и в более сложных инструментах наподобие Visual Studio Code, системы отслеживания ошибок Linear, и даже в Adobe Photoshop, а также во множестве других мест. Подозреваю, что они появляются повсюду потому, что сегодня подавляющее большинство людей привыкло задавать голосовым помощникам открытые вопросы наподобие Какая погода?, а power bar предоставляют аналог такой функциональности в отдельном приложении.

Строго говоря, power bar не является новой концепцией. Как пишет Мэттью Гуай в Capiche, потребовалось чуть меньше десятка лет для того, чтобы функция, добавленная разработчиком Джоном Скиннером во вторую версию Sublime Text, стала одной из важнейших новых функций программного обеспечения этого десятилетия.


Приложение-календарь Cron использует power bar.

По сути, изначально power bar появилась как инструмент, оптимизированный под опытных пользователей, например, инженеров, не желающих убирать руки от клавиатуры, но теперь начинает появляться повсюду, потому что даёт людям возможность быстрее выполнять их задачи без изучения документации или поисков в Google Search. Ещё важнее то, что она позволяет благодаря текстовому вводу найти горячие клавиши или функции, о которых пользователь даже не знал.

Больше всего в тенденции популяризации power bar меня восхищает то, что она помогает превращать людей в опытных пользователей, знающих, как пользоваться сложными функциями приложений гораздо быстрее, чем если бы им пришлось изучать их самим. Также power bar позволяют не забредать в тупики; если вы обыскали весь интерфейс пользователя, но не нашли нужной функции, power bar поможет вам при помощи простого текстового поиска.

Так как мы создаём и проектируем приложения, которые постепенно становятся всё сложнее, одна из самых сложных задач заключается том, чтобы помочь людям найти мощные функции, о которых они могли не знать. Подобные функции скрываются в меню, за таинственными значками или в раскрывающихся панелях под кнопкой "". Если вы не знаете, где находится то действие, которое вы хотите выполнить, то достаточно просто нажать CMD+K и ввести его, а не заниматься бесконечным щёлканьем.

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

Однако ознакомление людей с возможностями power bar может быть сложной задачей. Пустое поле поиска может выглядеть пугающим, особенно если не понимать, что его можно использовать не только для поиска. Приложения, в которых есть power bar, должны обучать пользователей их возможностям. Например, новые пользователи Superhuman и Cron знакомятся с программой через звонок в Zoom, чтобы они могли освоить эту концепцию.

Но как только люди освоят этот инструмент, они обретут уверенность и, скорее всего, начнут искать power bar в каждом инструменте, считая её стандартным паттерном.

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



На правах рекламы


Наши эпичные серверы хоть и не подходят для майнинга, но могут использоваться для любых других задач. Надёжные серверы на Linux или Windows с мощными процессорами семейства AMD EPYC и очень быстрой файловой системой, используем исключительно NVMe диски от Intel. Попробуйте как можно быстрее!

Присоединяйтесь к нашему чату в Telegram.

Подробнее..

Не баян ищем дубликаты изображений на основе Milvus с индексом FAISS внутри

22.12.2020 18:11:09 | Автор: admin


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

Под катом рассмотрим используемые инструменты, а потом перейдём к примеру реализации.

Свёрточная нейронная сеть (СNN)


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

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

Есть и другой недостаток. На выходе классификации получается большой вектор (2048 float для resnet152), который где-то нужно хранить и иметь возможность за разумный промежуток времени найти все N похожих векторов для искомого что само по себе уже непросто.

FAISS


Поиск наиболее близких векторов частая задача, для решения которой уже есть отличные инструменты. Здесь лидером считается библиотека FAISS от Facebook. Она использует эффективную кластеризацию векторов, позволяя организовывать поиск даже для векторов, которые не помещаются в RAM.

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

Milvus


Для этого есть весьма перспективный проект Milvus, который по дизайну сильно напоминает Elasticsearch. Отличие только в том, что Elasticsearch построен вокруг индекса lucene, а в Milvus вся архитектура выстроена вокруг индекса FAISS.

Структура коллекций тоже схожа:



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

Информация о коллекциях, партициях и сегментах хранится в отдельной SQL-базе. Для standalone-запуска используется встроенный SQLite, а ещё есть возможность использовать внешнюю MySQL-базу.



Проект Milvus находится в активной разработке (текущая версия 0.11.0). Пока что в нём нет репликации данных, как и возможности использовать другие SQL (или NoSQL) базы в качестве хранилища метаинформации. Поэтому пока для HA-решений можно использовать только схему с двумя экземплярами с общим хранилищем: один будет запущен, а другой спать. Для масштабирования можно будет использовать Mishards, но в 0.11.0 он сломан.

Кроме того, в 0.11.0 появилась возможность вместе с самим вектором и id сохранять в коллекцию дополнительные данные. Правда, пока без дополнительных индексов для них, но с возможностью поиска.

С точки зрения использования, Milvus выглядит как обычная внешняя база данных. Есть API (gRPC-клиент и набор http-методов) для сохранения и поиска вектора, управления коллекциями и индексами, а также для получения информации обо всех сущностях.

При создании коллекции можно указать максимальное количество записей в сегменте (segment_row_limit). Если превысить этот лимит, то Milvus начнёт строить индекс FAISS. С этим связана одна из особенностей Milvus: для всех добавляемых векторов, по которым ещё не создан индекс, поиск будет работать на основе полного перебора. Поэтому при больших значениях segment_row_limit будет много записей, для которых индекс ещё не построен (он также влияет ещё и на то, сколько будет созданных сегментов для коллекции). Для поиска похожих векторов в коллекции необходимо сделать поиск в каждом сегменте и чем их больше, тем дольше поиск.

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

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

Реализация поиска дубликатов


Milvus можно запускать как в CPU-версии, так и в GPU. Первую лучше всего использовать на процессорах, которые поддерживают инструкцию AVX512. Для этого достаточно просто запустить контейнер:

docker run -d --rm --name milvusdb -p 19530:19530 -p 19121:19121 \     milvusdb/milvus:0.11.0-cpu-d101620-4c44c0

В данном случае 19530 будет портом для gRPC-клиента, а 19121 для http API.

В качестве CNN-сети можно взять любую из предобученных (или обучить самому) результаты могут немного отличаться. В данном примере будем использовать предобученный resnet152:

model = models.resnet152(pretrained=True)

Вектор будем снимать со слоя `avgpool`:

layer = model._modules.get('avgpool')

А сам вектор получать с помощью hook:

vector = torch.zeros(2048)def copy_data(m, i, o):    vector.copy_(torch.reshape(o.data, (1, 2048))[0])hook = layer.register_forward_hook(copy_data)model(prepared_image)hook.remove()

Полный код получения вектора выглядит так:

import numpy as npimport torchimport torchvision.models as modelsimport torchvision.transforms as transformsfrom torch.autograd import Variablefrom PIL import Imagemodel = models.resnet152(pretrained=True)layer = model._modules.get('avgpool')model.eval()pipeline = [    transforms.Resize(224),    transforms.ToTensor(),    transforms.Normalize(mean=[0.485, 0.456, 0.406],                         std=[0.229, 0.224, 0.225]),]def _prepare_Image(img: Image) -> Variable:    raw = img    for action in pipeline:        raw = action(raw)    return Variable(raw.unsqueeze(0))def image_vectorization(image_path: str) -> np.ndarray:    img = Image.open(image_path)    prepared_image = _prepare_Image(img)    vector = torch.zeros(2048)    def copy_data(m, i, o):        vector.copy_(torch.reshape(o.data, (1, 2048))[0])    hook = layer.register_forward_hook(copy_data)    model(prepared_image)    hook.remove()    # vector normalization    norm_vector = vector / torch.norm(vector)    return np.array(norm_vector)

Теперь понадобится клиент для работы с Milvus. Можно взять любой из поддерживаемых (Python, Java, Go, Rest, C++). Возьмём Java-клиент и напишем пример на Kotlin. Почему? А почему бы и нет.

Подключаем Milvus SDK:

implementation("io.milvus:milvus-sdk-java:0.9.0")

Создаём подключение к Milvus:

val connectParam = ConnectParam.Builder()        .withHost("localhost")        .withPort(19530)        .build()val client = MilvusGrpcClient(connectParam)

Создаём коллекцию под 2048 вектор:

val collectionMapping = CollectionMapping.create(collectionName)            .addVectorField("float_vec", DataType.VECTOR_FLOAT, 2048)          //выключаем автосоздание id            .setParamsInJson(JsonBuilder()                  .param("auto_id", false)                  .param("segment_row_limit", segmentRowLimit)                  .build()            )        client.createCollection(collectionMapping)

Создаём IVF_SQ8 индекс:

    Index.create(collectionName, "float_vec")            .setIndexType(IndexType.IVF_SQ8)            .setMetricType(MetricType.L2)            .setParamsInJson(JsonBuilder()                    .param("nlist", 16384)                    .build()            )     client.createIndex(index)

Сохраняем несколько векторов в коллекцию:

InsertParam.create(collectionName)            .setEntityIds(listOf(1L, 2L))            .addVectorField("float_vec", DataType.VECTOR_FLOAT, listOf(vector1, vector2))client.insert(insertParam)client.flush(collectionName)  // чтобы сразу можно было найти  вектор

Ищем ранее сохранённый вектор:

val dsl = JsonBuilder().param(            "bool", mapOf(                "must" to listOf(                    mapOf(                        "vector" to mapOf(                            "float_vec" to                                mapOf(                                    "topk" to 10,                                    "metric_type" to MetricType.L2,                                    "type" to "float",                                    "query" to listOf(vector1),                                    "params" to mapOf("nprobe" to 50)                                )                        )                    )                )            )        ).build() val searchParam = SearchParam.create(collectionName)       .setDsl(dsl) val result = client.search(searchParam) println(result.queryResultsList[0].map { it.entityId to it.distance })

Если всё работает и правильно настроено, то вернётся похожий результат:

[(1, 0.0), (2, 0.2)]

Для первого вектора расстояние L2 с самим собой будет 0, а с другим вектором больше 0.

Всё вышеприведённое, конечно, только наброски, но этого достаточно, чтобы попробовать создать Python-сервис для классификации и получения вектора. И либо для него накрутить API для сохранения и поиска векторов, либо сделать в отдельном сервисе (например, на Kotlin), который будет получать вектор и сохранять его уже в Milvus самостоятельно.

Спасибо всем, кто дочитал до конца, надеюсь, вы нашли для себя что-то новое. А если вас заинтересовал проект Milvus, то можете поддержать его на Github.
Подробнее..

Полнотекстовый поиск в Couchbase Server

27.11.2020 10:16:55 | Автор: admin
Дмитрий Калугин-Балашов большую часть своей жизни писал поиск: с 2011 года в компании Mail.ru был поиск по почте, затем был небольшой перерыв из-за работы в США, а сейчас это работа над поиском в Couchbase. Одна из первых вещей, которую Дмитрий понял, работая в США не всегда покупают самое эффективное решение. Иногда покупают то, где клиент будет иметь меньше проблем.

Поэтому ещё в 2013 году Дмитрий написал движок поиска для почтовых ящиков Mail.ru и рассказал об этом в том же году на конференции HighLoad и в статье на Хабре. А на HighLoad 2019 показал, как устроен полнотекстовый поиск в Couchbase Server, и сегодня мы предлагаем расшифровку его доклада.



На самом деле в Couchbase используется внешний опенсорсный движок Bleve:

Этот движок изначально был создан внутри Couchbase как проект для реализации полнотекстового поиска в виде библиотеки, написанной на Go. Сначала он был простым монолитным индексом если есть хранилище ключ-значение, то слева берём слова в виде ключей, а в виде значений ставим документы и всё, у нас есть полнотекстовый поиск! Но в работе он становился очень большим, а обычное хранилище k-value под поиск не оптимизировано. Нагрузка под поиск очень специфичная, а если ещё и апдейты есть, то движок неизбежно начинает тупить.

Поэтому Couchbase решили создать сегментированный индекс специальное хранилище под поисковые данные, в котором используются Immutable-сегменты, оптимизированные под поиск.

Что такое обратный индекс? Если есть книга, в которой я хочу что-то найти по слову как это сделать? На последней странице обычно есть указатель на слово и страницу, где этот термин встречается. Обратный индекс сопоставляет слова со списком. В книге это страницы, у нас документы в базе (у нас документно-ориентированная БД, в которой хранятся джейсонки).

ОК, а как хранить список слов?


Map[string]uint. Самое простое. Кто владеет Гошкой, знает, что это хэш-таблица. Но это не очень хорошо, потому что у нас используются слова, и иногда надо по ним итерироваться. Если у нас неточный поиск, мы начинаем считать расстояние Левенштейна, нужно итерироваться местами, а это по hashmap нельзя. Но можно сделать дерево.

Tree. Но дерево оно здоровое, поэтому тоже не годится, потому что мы делаем хранилище под поиск, а это одновременно и общее хранилище для БД.

Trie. Префиксное дерево это уже лучше, оно довольно компактное, его можно через два массива построить. Но можно ли сделать лучше?

Finite State Transducer. Это автомат под названием Vellum, который Couchbase для себя же и написали. Есть реализация FST на Go и других языках. В нем есть всего лишь три процедуры:
  • Построение FST (один раз);
  • Поиск;
  • Итерирование.

Чтобы построить, нужен список слов в алфавитном порядке (например, возьмём are, ate, see). Так как у нас индексы сегментированные (сегменты неизменяемые), то нам это отлично подходит мы его построили один раз и забыли.

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



Таким образом слово are дает 4. Добавляем второе слово:



Добавляем третье слово:



Теперь мы делаем еще одну операцию и компактим:



Получается автомат, в котором всего два объекта:
  1. Builder мы строим, операцию делаем;
  2. Reader мы читаем и делаем либо поиск, либо итерацию.

Открыть его мы можем либо из файла через mmap (если он поддерживается в системе):
fst, err := vellum.Open("/tmp/vellum.fst")

либо из массива Bytes (это нам будет очень нужно дальше), и можем загрузить прямо из буфера:
fst, err := vellum.Load(buf.Bytes())

Дальше запускаем поиск в FST:
val, exists, err = fst.Get([]byte("dog"))


Как хранить PostingsList?


К списку слов есть список страниц в книге, а в БД PostingsList список документов. Как его можно хранить?
  • Просто список чисел это массив (кстати, можно использовать дельта-кодирование);
  • Битовый массив. Это удобно, если, что список чисел, как страницы в книге, начинается просто от 0 и числа идут подряд. Но только если в нем не будет повторяемых элементов.
  • RLE-сжатие. Битовый массив можно еще иногда сжать.

Как сделать лучше? Есть RoaringBitmap, с библиотеками почти для всех языков. Работает он так: берем последовательность наших чисел и разбиваем на куски (чанки). Каждый чанк кодируем одним из трех способов:
  1. Просто список чисел
  2. Битовый массив
  3. RLE-сжатие

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

Поиск Memory-Only?


Теперь попробуем сделать полнотекстовый поиск Memory-Only. У нас есть Snapshot это наш индекс на какую-то единицу времени. В нём есть несколько сегментов с данными. Внутри сегментов ищем обратные индексы, которые все read-only. Также есть какое-то приложение, которое работает с нашим поиском:



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



Но сами сегменты нам не нужны мы копируем их под read mutex и добавляем новый сегмент. Маленькие фрагменты это просто битовые массивы. Если что-то удаляется, мы просто выставляем там биты:



То есть сам сегмент read only (immutable), но удалить что-то можно, выставив эти биты, если в новом сегменте пришла эта информация. Это называется Segment Introduction: есть новый сегмент (битовый массив) и горутина (Introducer). Ее задача добавить сегмент в наш Snapshot, который мы присылаем по каналу:



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



И все мы переходим к четвертому Snapshot и получаем Memory-Only решение:



Для того, чтобы сохранить на диск, есть еще одна горутина Persister. У нас есть номера эпох, также пронумеруем сегменты (a, b, c, d):



В Persister есть цикл FOR, время от времени он пробуждается и смотрит О! Появился новый сегмент, давайте его сохраним:



Для этого сегмента у нас две базы данных: маленькая БД, куда он сохраняется, и корневая БД, которая просто описывает всю эту конструкцию. Таким образом мы сохраняем на диск и меняем номер эпохи после записи:



Сегменты будут расти (они все неизменяемые), когда-то мы захотим их склеить и для этого у нас есть merger. Создаем Merge plane третьей горутиной:



Также есть номер эпохи, когда мы мерджим. Далее мы делаем какие-то операции. Merge Introduction, как и обычный сегмент Introduction, попадает обратно в него и создает новый сегмент, а два склеивает, удаляя оттуда данные:



Таким образом мы удаляем часть сегментов.

Важное примечание. После того, как мы записали zap-файл на диск, мы сегмент закрываем и тут же обратно открываем, но не как in-memory сегмент, а как сегмент на диске. Хотя у них одинаковый интерфейс, устроены они по-разному: сегмент на диске проецируется в память через mmap.

Формат ZAP


Изначально это был BoltDB. Потом создатель сказал: Ну вас всех на фиг, я устал, я ухожу, и BoltDB закрылся. Хотя позже его форкнули в BBoltDB, работал он всё равно плохо, потому что вернулись к той же проблеме: база данных в общем не очень подходит для хранения поисковых индексов. И мы тогда заменили BBoltDB на самописный формат .zap. Корневая база осталась BBoltDB, но там ничего особо критичного хранить не надо:



Формат ZAP это просто сегмент на диске. Мы его не читаем READами, он читается mmapом, при этом данные внутри максимально удобны для in-memory работы. Сам формат ZAP с индексами выглядит так:



В футере описано, где что искать, и мы по указателю находим индекс полей. Что у нас есть? Есть сколько-то (от 0 до F#) проиндексированных полей в документах, они пронумерованы и проиндексированы. Стрелками показаны указатели, сколько полей, и мы знаем заранее, сколько их. Мы находим в Fields Index указатель, а в Fields место, и понимаем по названию поля, что это такое:



Есть еще один указатель:



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



Этот указатель показывает VELLUM данные:



А мы помним, что VELLUM может читать данные прямо из памяти. Мы спроецировали память, нашли нужный указатель, взяли кусок данных и начали с помощью VELLUM в нём искать. Нам даже не нужны READ, мы просто к списку слов делаем список OFFSET, который ведёт к RoaringBitmap. Таким образом мы получаем номера документов, которые найдутся по этому слову в этом поле:



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



DocValues это вспомогательный и необязательный список полей, в нём хранятся значения полей:



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



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



Остаются мелочи. Это Chunk Factor(на какие кусочки бьем, когда делаем чанки данных), версия, контрольная сумма и число документов (D#):



Это весь ZAP-файл. Как искать, думаю, уже понятно: мы отражаем в память и находим, по какому полю ищем. После этого находим слово, идем по длинной цепочке и находим результаты.

Rollback


Есть интересная операция Rollback. Базе данных Rollback нужен всегда мы можем нагородить огород, и нам нужна возможность откатиться. В нашем случае откат делается очень просто. У нас есть эпохи. Мы храним в корневой базе текущую конфигурацию и предыдущие (1-2), куда хотим откатиться. Если мы хотим откатиться на предыдущую эпоху, мы достаем конфигурацию из индекса, и получаем откат на предыдущую эпоху. В сегмент мы добавляем, когда происходит какая-то операция или группа операций (мы обычно их вставляем большой группой):



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

Так работает само ядро. Как работает поиск уже очевидно. Если поиск идет по точному совпадению, то прочитали, прошли по цепочке и нашли. Если поиск неточный, то добавляется автомат Левенштейна и начинаются итерации по vellum, но процесс проходит примерно так же.

Пока пандемия идет к финишу, мы продолжаем проводить встречи онлайн. В понедельник 30 ноября будем обсуждать банковскую архитектуру на встрече Эволюция через боль. Как устроен СберБанк Онлайн изнутри?. Рассмотрим с нуля, как создается и развивается архитектура в банке и узнаем, что под капотом у крупнейшего банка страны. Узнаем, почему банку недостаточно простого приложения из одного сервиса и одной базы данных и увидим на примерах монолит и микросервис. Начало в 18 часов.

А 3 декабря будет митап Безопасность и надёжность в финтехе. Спикеров будет несколько, и они поднимут несколько тем. Сначала будет разговор о закулисье финтеха, затем как и какими инструментами сделать финтех надежным сервисом, и на закуску как снизить риски ИБ в разработке финансовых систем. Начнем в 17.

Следите за новостями Telegram, Twitter, VK и FB и присоединяйтесь к обсуждениям.
Подробнее..

Устройство поисковых систем базовый поиск и инвертированный индекс

21.03.2021 12:21:06 | Автор: admin


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

В статье описано устройство поиска, инвертированного индекса и его оптимизаций с отсылками к теории. В качестве подопытного кролика взят Tantivy реализация архитектуры Lucene на Rust. Статья получилась концентрированной, математикосодержащей и несовместимой с расслабленным чтением хабра за чашкой кофе, осторожно!


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

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

Определение релевантности


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

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

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

  • Берем любимую библиотеку построения эмбеддингов для текстов, например fastText или BERT, преобразуем документы в вектора
  • Складываем полученные вектора в любимую библиотеку для поиска K ближайших соседей (k-NN) к данному вектору, например в faiss
  • Поисковый запрос преобразовываем в вектор тем же методом, что и документы
  • Находим ближайшие вектора к запросу-вектору и извлекаем соответствующие найденным векторам документы

Поиск, основанный на k-NN, будет очень медленным, если вы попытаетесь засунуть в него весь Интернет. Поэтому мы сузим определение релевантности так, чтобы всё стало вычислительно проще.

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

NB.: Здесь и далее слова в контексте документов и запросов будут называться термами с целью избежания путаницы

Запишем релевантность в виде двух математических функций и далее будем наполнять их содержанием:

  • $score(q, d)$ релевантность документа запросу
  • $score(t, d)$ релевантность документа одному терму

На $score(q, d)$ наложим ограничение аддитивности и выразим релевантность запроса через сумму релевантностей термов:

$score(q, d)=\sum_{t \in q}score(t, d)$

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

Наиболее известные аддитивные функции релевантности TF-IDF и BM25. Они используются в большинстве поисковых систем как основные метрики релевантности.

Откуда взялись TF-IDF и BM25


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

И TF-IDF, и BM25 показывают степень релевантности документа запросу одним числом. Чем выше значение метрик, тем более релевантен документ. Сами по себе значения не имеют какой-либо значимой интерпретации. Важно только сравнение значений функций для различных документов. Один документ более релевантен данному запросу, чем другой, если значение его функции релевантности выше.

Попробуем повторить рассуждения авторов формул и воспроизвести этапы построения TF-IDF и BM25. Обозначим размер корпуса проиндексированных документов как $N$. Самое простое, что можно сделать это определить релевантность равной количеству вхождений терма (termFrequency или $tf$) в документ:

$score(t, d)=tf(t, d)$

Что делать, если у нас не один терм $t$, а запрос $q$, состоящий из нескольких термов, и мы хотим посчитать $score(q, d)$ запроса для этого документа? Вспоминаем про ограничение аддитивности и просто суммируем все отдельные $score(t, d)$ по термам из запроса:

$score(q, d)=\sum_{t \in q}score(t, d)$

В формуле выше есть проблема мы не учитываем различную важность разных термов. Если у нас будет запрос cat and dog, то наиболее релевантными окажутся документы, в которых есть 100500 вхождений терма and. Вряд ли это то, что хотел бы получить пользователь.

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

$score(t, d)=\frac{tf(t, d)}{df(t)}$

$df(t)$ это количество документов в корпусе, содержащих терм $t$. Получается, что чем чаще терм встречается, тем менее он важен и тем меньше будет $score(t, d)$. Термы вроде and будут иметь огромный $df(t)$ и соответственно маленький $score(t, d)$.

Вроде уже лучше, но теперь есть другая проблема сам по себе $df(t)$ мало о чём говорит. Если $df(жираф) = 100$, и размер корпуса проиндексированных текстов 100 документов, то терм жираф в этом случае считается очень частым. А если размер корпуса 100 000, то уже редким.

Зависимость $df(t)$ от $N$ может быть убрана превращением $df(t)$ в относительную частоту путем деления на $N$:

$score(t, d)=\frac{tf(t, d)}{\frac{df(t)}{N}}=tf(t, d)\frac{N}{df(t)}$

Теперь представим следующее у нас 100 документов, в одном из них есть терм слон, в двух жираф. $\frac{N}{df(t)}$ в первом случае будет равно 100, а во-втором 50. Терм жираф получит в два раза меньше очков, чем терм слон только лишь потому, что документов с жирафом на один больше, чем со слоном. Исправим эту ситуацию, сгладив функцию $\frac{N}{df(t)}$.

Сглаживание можно произвести различными способами, мы сделаем это логарифмированием:

$score(t, d) = tf(t, d)\log\frac{N}{df(t)}$

Мы только что получили TF-IDF. Едем дальше к BM25.

Вряд ли документ, содержащий терм жираф 200 раз в два раза лучше, чем документ, содержащий терм жираф 100 раз. Поэтому и тут проедемся сглаживанием, только теперь сделаем это не логарифмированием, а чуть иначе. Заменим $tf(t, d)$ на $tf_s(t, d) = \frac{tf(t, d)}{tf(t, d) + k}$ С каждым увеличением числа вхождения терма $tf(t, d)$ на единицу, значение $tf_s(t, d)$ прирастает все меньше и меньше функция сглажена. А параметром $k$ мы можем контролировать кривизну этого сглаживания. Говоря по-умному, параметр $k$ контролирует степень сатурации функции.


Рис. 0: Чем выше значение $k$, тем сильнее будут учитываться последующие вхождения одного и того же терма.

У $tf_s(t, d)$ есть два замечательных побочных эффекта.

Во-первых, $score(q, d)$ будет больше у документов, содержащих все слова из запроса, чем у документов, которые содержат одно слово из запроса несколько раз. Топ документов в этом случае будет больше радовать глаз и ум пользователя, ведь все термы запроса обычно печатаются не просто так.

Во-вторых, значение функции $tf_s(t, d)$ ограничено сверху. Остальная часть $score(t, d)$ тоже ограничена сверху, поэтому и вся функций $score(t, d)$ имеет ограничение сверху (далее $UB_t$ upper bound). Более того, $UB_t$ в нашем случае очень просто посчитать.

Почему $UB_t$ важно для нас? $UB_t$ является максимально возможным вкладом этого терма в значение функции релевантности. Если мы знаем $UB_t$, то можем срезать углы при обработке запроса.

Последний шаг начнем учитывать длину документов в $score(t, d)$. В длинных документах терм жираф может встретится просто по-случайности и его наличие в тексте ничего не скажет о реальной теме документа. А вот если документ состоит из одного терма и это терм жираф, то можно совершенно точно утверждать, что документ о жирафах.

Очевидный способ учесть длину документа взять количество слов в документе $dl(d)$. Дополнительно поделим $dl(d)$ на среднее количество слов во всех документах $dl_{avg}$. Сделаем мы это исходя из тех же соображений, из каких нормировали $df(t)$ выше абсолютные значения портят качество метрики.

Найдем теперь место для длины документа в нашей формуле. Когда $k$ растет, то значение $tf_s$ падает. Если мы будем умножать $k$ на $\frac{dl(d)}{dl_{avg}}$, то получится, что более длинные документы будут получать меньший $score(t, d)$. То что нужно!

Можно еще дополнительно параметризовать силу, с которой мы учитываем длину документа, для контроля поведения формулы в разных ситуациях. Заменим $\frac{dl(d)}{dl_{avg}}$ на $1 b + b\frac{dl(d)}{dl_{avg}}$ и получим:

$\frac{tf_s(t, d)}{tf_s(t, d) + k(1 - b + b\frac{dl(d)}{dl_{avg}})}$

При $b = 0$ формула вырождается в $\frac{tf_s(t, d)}{tf_s(t, d) + k}$, а при $b = 1$ формула принимает вид $\frac{tf_s(t, d)}{tf_s(t, d) + k\frac{dl(d)}{dl_{avg}}}$.

Ещё раз: $k$ сила влияния повторяющихся термов на релевантность, а $b$ сила влияния длины документа на релевантность.

Подставим $tf$ в $tf_s$:

$score(q, d)=\sum_{t \in q} \frac{tf(t, d) (k + 1)}{tf(t, d) + k(1 - b + b\frac{dl(d)}{dl_{avg}})} * \log\frac{N}{df(t)}$

У нас получилась формула BM25 с небольшим нюансом. В каноничной формуле $\log\frac{N}{df(t)}$ (этот член называется $IDF$) заменен на $\log\frac{N - df(t) + 0.5}{df(t) + 0.5}$. Замена не имеет простых эвристик под собой и основана на подгонке под теоретически более чистую форму RSJ модели. Такая форма $IDF$ дает меньший вес слишком часто встречающимся термам: артиклям, союзам и прочим сочетаниям букв, несущим малое количество информации.

Важное замечание: из формулы BM25 теперь видно, что $UB_t$ в бОльшей мере зависит от значения $IDF$, то есть от частоты терма в корпусе. Чем реже встречается терм, тем выше его максимально возможный вклад в $score(q, d)$.

Реализация инвертированного индекса


С учетом ограниченной памяти, медленных дисков и процессоров нам теперь нужно придумать структуру данных, способную выдавать top-K релевантных по BM25 документов.

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

terms-and-posting-lists

Рис. 1: Постинг-листы

Различные реализации инвертированных индексов также могут сохранять точные места в документе, где встречается терм или общее количество вхождений терма в документ. Эта дополнительная информация используется при подсчете метрик релевантности или для выполнения специфичных запросов, в которых важно взаимное расположение термов в документе. Сам постинг-лист сортируется по возрастанию DID, хотя существуют и иные подходы к его организации.

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


Рис. 2: Словарь термов (префиксное дерево)

В Tantivy для хранения термов вообще использованы finite-state transducers через crate fst. Если совсем упрощать, то можно считать, что префиксные деревья организуют словарь путем выделения общих префиксов у ключей, а трансдюсеры ещё могут и общие суффиксы выделять. Поэтому сжатие словаря происходит эффективнее, только в итоге получается уже не дерево, а ациклический орграф.

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

Ещё в fst есть методы сериализации и десериализации словаря, что сильно облегчает жизнь складывать руками графы и деревья на диск то ещё развлечение. И в отличии от хеш-таблиц, fst позволяет использовать подстановку (wildcard) при поиске по ключам. Говорят, некоторые таинственные люди пользуются звездочкой в поисковых запросах, но я таких не видел.

Используя словарь термов и постинг-листы можно для запроса из одного одного терма $t$ определить список документов, в котором этот терм появляется. Затем останется посчитать $score(t, d)$ для каждого документа из постинг-листа и взять top-K документов.

Для этого перенесем $score(t, d)$ из области математики в реальный мир. В Tantivy используется BM25, как один из вариантов функции релевантности:

$score(t, d)=\sum_{t \in q} \frac{tf(t, d) (k + 1)}{tf(t, d) + k(1 - b + b\frac{dl(d)}{dl_{avg}})} * \log\frac{N - df(t) + 0.5}{df(t) + 0.5}$

  • $tf(t, d)$ подсчитываем количество DID документа в постинг-листе терма $t$, либо храним отдельным числом, что ускорит весь процесс за счет использования дополнительной памяти. Tantivy использует последний вариант: упаковывает DID в блоки по 128 чисел, а затем пишет блок из 128 частот термов, используя bitpack-кодировку
  • $df(t)$ длина всего постинг-листа
  • $dl_{avg}$ рассчитываем на основе двух статистик, общего количества документов в индексе и суммарной длины всех постинг-листов. Обе статистики поддерживаются инвертированным индексом в актуальном состоянии при добавлении нового документа
  • $dl(d)$ храним для каждого документа в отдельном списке

Кроме словаря термов, постинг-листов и длин документов Tantivy (и Lucene) сохраняет в файлах:

  • Точные позиции слов в документах
  • Скип-листы для ускорения итерирования по спискам (об этом дальше)
  • Прямые индексы, позволяющие извлечь сам документ по его DID. Работают прямые индексы медленно, так как документы хранятся сжатыми тяжелыми кодеками типа brotli
  • FastFields встроенное в инвертированный индекс быстрое KV-хранилище. Его можно использовать для хранения внешних статистик документа а-ля PageRank и использовать их при расчете вашей модифицированной функции $score(q, d)$

Теперь, когда мы можем посчитать $score(q, d)$ для запроса из одного терма, найдем top-K документов. Первая идея посчитать для всех документов их оценки, отсортировать по убыванию и взять К первых. Потребуется хранить всё в RAM и при большом количестве документов у нас кончится память.

Поэтому при обходе постинг-листа от начала и до конца первые $K$ документов кладутся в кучу (далее top-K heap) безусловно. А затем каждый последующий документ сначала оценивается и кладется в кучу только если его $score(q, d)$ выше минимального $score(q, d)$ из кучи. Текущий минимум в top-K heap далее будет обозначен как $\theta$.

Операции над постинг-листами для запросов из нескольких термов


Что сделает инвертированный индекс с запросом скачать OR котики? Он заведет два итератора по постинг-листам для термов скачать и котики, начнет итерирование по обоим листам, попутно рассчитывая $score(q, d)$ и поддерживая top-K heap.

Аналогичным образом реализуется AND-запрос, однако тут итерирование позволяет пропускать значительные части постинг-листов без расчета $score(q, d)$ для них.

Более важными для поисковиков общего назначения являются OR-запросы. А всё потому, что они покрывают больше документов и потому, что ранжирование запросов метриками TF-IDF или BM25 всё равно поднимает в топ документы с бОльшим количеством совпавших слов. Это сделает top-K документов больше похожим на результат работы AND-запроса.

Наивный алгоритм реализации OR-запроса следующий:

  1. Создаем итераторы для постинг-листов каждого терма из запроса
  2. Заводим top-K heap
  3. Сортируем итераторы по DID, на которые они указывают
  4. Берем документ, на который указывает первый итератор и собираем среди оставшихся итераторов те, которые указывают на тот же документ. Так мы получим DID и термы, которые содержатся в этом документе
  5. Рассчитываем релевантность документа по термам, складываем их, получаем релевантность по всему запросу. Если документ хороший, то кладем его в top-K heap
  6. Продвигаем использованные итераторы и возвращаемся к п.3



Рис. 3: Итерации OR-алгоритма. Чуть ниже есть псевдокод алгоритма

В п.4 сбор итераторов осуществляется быстро, так как список итераторов отсортирован по DID. Пересортировку итераторов в п.3 тоже можно оптимизировать, если мы знаем какие итераторы были продвинуты в п.6.

Некоторые оптимизации инвертированного индекса


В обычной задаче поиска ищутся не вообще все релевантные документы, а только K наиболее релевантных. Это открывает путь для важных оптимизаций. Причина простая большая часть документов станет ненужной и мы избежим накладных вычислений над ней. Такая постановка задачи ещё известна как Top-K queries.

Посмотрим внимательнее на псевдокод OR-алгоритма Bortnikov, 2017:
Input:  termsArray - Array of query terms  k - Number of results to retrieveInit:  for t  termsArray do t.init()  heap.init(k)    0  Sort(termsArray) Loop:   while (termsArray[0].doc() < ) do    d  termsArray[0].doc()    i  1    while (i < numTerms  termArray[i].doc() = d) do      i  i + 1    score  Score(d, termsArray[0..i  1]))    if (score  ) then         heap.insert(d, score)    advanceTerms(termsArray[0..i  1])     Sort(termsArray)Output: return heap.toSortedArray()function advanceTerms(termsArray[0..pTerm])   for (t  termsArray[0..pTerm]) do    if (t.doc()  termsArray[pTerm].doc()) then       t.next()

Наивный алгоритм работает с асимптотикой $O(LQ\log{Q})$, где L суммарная длина используемых при обработке запроса постинг-листов, а Q количество слов в запросе. Немного обнаглев, из оценки можно выкинуть $Q\log{Q}$ подавляющее большинство пользователей приносит запросы не длиннее какого-то максимума и можно считать $Q\log{Q}$ константой.

На практике, наибольший вклад в скорость работы инвертированного индекса вносит размер корпуса (т.е длины постинг-листов) и частота запросов. Включенное автодополнение запроса или внутренние аналитические запросы в поисковую систему способны кратно умножить нагрузку на систему. Даже $O(L)$ в такой ситуации оказывается слишком грустной оценкой.

Сжатие постинг-листов


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

Вспомним, что постинг-лист это возрастающий список DID, сами DID обычно 64-битные беззнаковые числа. Числа в постинг-листе не сильно отличаются друг от друга и лежат в достаточно ограниченной области значений от 0 до некоторого числа, сопоставимого с количеством документов в корпусе.

VarLen Encoding

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

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

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

SIMD

Для возможности параллельного чтения изобрели компромиссные схемы, похожие на varint, но не совсем. В таких схемах числа разбиваются на группы по N чисел и каждое число в группе кодируются одинаковым количеством бит, а вся группа предваряется дескриптором, описывающим что в группе находится и как это распаковать. Одинаковая длина запакованных чисел в группе позволяет использовать SIMD-инструкции (SSE3 в Intel) для распаковки групп, что кратно ускоряет время работы.

Delta-encoding

Varint хорошо сжимает малые числа и хуже сжимает большие числа. Так как в постинг-листе находятся возрастающие числа, то с добавлением новых документов качество сжатия будет становиться хуже. Простое изменение в постинг-листе будем хранить не сами DID, а разницу между соседними DID. Например, вместо [2, 4, 6, 9, 13] мы будем сохранять [2, 2, 2, 3, 4].

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

Скип-листы для итерирования по постинг-листам


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

Есть такая замечательная штука скип-листы. Скип-лист живет рядом со связанным списком чисел и представляет из себя разреженный индекс по содержанию этого списка. Если вы хотите в списке чисел найти Х, то скип-лист за время $O(log(L))$ пояснит вам, куда именно надо прыгнуть, чтобы оказаться в вашем связанном списке примерно в нужном месте перед Х. После прыжка вы уже обычным линейным поиском идете до Х.

Точность прыжка зависит от объема памяти, который мы можем выделить под скип-лист типичный компромисс в алгоритмах. В Tantivy перемещение вдоль постинг-листа реализовано именно скип-листами. Известна lock-free реализация скип-листа, но на момент написания статьи (март 2021) библиотека выглядит не слишком поддерживаемой.

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

Оптимизации OR-запросов


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

MaxScore

MaxScore одна из первых известных попыток ускорить выполнение OR-запросов. Оптимизация относится к безопасным, описана в Turtle, 1995.

Суть оптимизации в разбиении термов запроса на два непересекающихся множества: обязательных и необязательных. Документы, содержащие термы только из необязательного множества, не могут войти в top-K и поэтому их постинг-листы могут быть промотаны вперед до первого документа, который содержит хотя бы один обязательный терм.

Помните $UB_t$ терма, введеный в разделе про TF-IDF и BM25? Напоминаю, что это максимально возможный вклад терма в релевантность любого запроса. $UB_t$ является функцией от $IDF$ и рассчитывается на лету.

Имея на руках $UB_t$, можно отсортировать все термы из запроса по убыванию их $UB_t$ и посчитать частичные суммы $UB_t$ от первого и до последнего терма. Все термы с частичной суммой меньше текущего $\theta$ можно отнести к необязательному множеству. Документ, содержащий термы только из необязательного множества не может быть оценен выше, чем сумма $UB_t$ этих документов. Стало быть, такой документ не войдет в итоговое множество.

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


Рис. 4: Промотка итераторов в MaxScore

После каждого обновления top-K heap производится перестроение двух множеств и алгоритм завершает работу в момент, когда все термы оказываются в необязательном множестве.

Weak AND (WAND)

WAND также является безопасным методом оптимизации поиска, описанным в Broder, 2003. В чем-то он похож на MaxScore: также анализирует частичные суммы $UB_t$ и $\theta$.

  1. Все итераторы термов WAND сортирует в порядке DID, на который указывает каждый итератор
  2. Рассчитываются частичные суммы $UB_t$
  3. Выбирается pivotTerm первый терм, чья частичная сумма превосходит $\theta$
  4. Проверяются все предшествующие pivotTerm'у итераторы.

    Если они указывают на один и тот же документ, то этот документ теоретически может входить в top-K документов и поэтому для него производится полноценный рассчет $score(q, d)$.

    Если хотя бы один из итераторов указывает на DID, меньший чем pivotTerm.DID, то такой итератор продвигается вперед до DID, большего чемpivotTerm.DID.

    После этого, мы возвращаемся на первый шаг алгоритма


Block-max WAND (BMW)

BMW является расширением алгоритма WAND из предыдущего пункта, предложенным в Ding, 2011. Вместо использования глобальных $UB_t$ для каждого терма, мы теперь разбиваем постинг-листы на блоки и сохраняем $UB$ отдельно для каждого блока. Алгоритм повторяет WAND, но проверяет в дополнение еще и частичную сумму $UB$ блоков, на которые сейчас указывают итераторы. В случае, если эта сумма ниже $\theta$, то блоки пропускаются.

Блочные оценки $UB$ термов в большинстве случаев гораздо ниже глобальных $UB_t$. Поэтому многие блоки будут скипнуты и это позволит сэкономить время на расчете $score(q, d)$ документов.

Для понимания разрыва между продакшеном инвертированных индексов и академическими исследованиями можно занырнуть в широко известный в узких кругах тикет LUCENE-4100.

TLDR: Реализация важной Block-max WAND-оптимизации молчаливо дожидалась смены TF-IDF на BM25, заняла 7 лет и была выкачена только в Lucene 7.

Block Upper Scoring

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

Автор статьи экспериментировал с поиском, в котором необходимы были только свежие документы-новости. BM25 была заменена на $BM25D(q, d, time) = BM25(q, d) * tp(time)$ где $tp$ функция, накладывающая пенальти на устаревающие документы и принимающая значения от 0 до 1. Поменяв формулу для $UB$, удалось добиться пропуска 95% всех блоков с несвежими новостями, что сильно ускорило конкретно этот вид поиска. Сам подход с хранением поблочных метрик, а также вычислимым и конечным пределом функции релевантности располагает к экспериментам.

Предварительная обработка поискового запроса


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

Разбор поискового запроса



Рис. 5: Этапы обработки поискового запроса

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

Будут ли токены соединены по умолчанию оператором OR или AND зависит от настроек индекса. С одной стороны соединение через AND иногда может дать более точный результат, с другой есть риск вообще ничего не найти. Можно составить несколько запросов и после их выполнения на основе размера выдачи или внешних метрик выбрать лучший вариант.

Логическое дерево ложится в основу плана операций. В Tantivy соответствующая структура называется Scorer. Реализация Scorer является центром вселенной инвертированных индексов, так как эта структура ответственна за итерирование постинг-листов и за все возможные оптимизации этого процесса.

Этап расширения поискового запроса


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

Запрос разбавляется синонимами, термам добавляются веса, используется история предыдущих поисков, добавляются фильтры, контекст поиска и миллион других хаков, являющихся коммерческими секретами. Этот этап работы называется query extension, он невероятно важен для улучшения качества поиска.

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

Например:
скачивать, скачать, скачал, скачали -> скачатькотики, котиков, котикам -> котик

У вас есть несколько вариантов:

  • Тяжелый: сразу попытаться научить инвертированный индекс хранить и леммы, и словоформы, а также научиться учитывать их при поиске. Нужно много программировать и проводить переиндексацию корпуса документов.
  • Компромиссный: переиндексировать весь корпус документов, приводя все словоформы к леммам на лету, а также лемматизировать приходящие запросы. Меньше программирования, но все так же требуется переиндексация.
  • Простой: разбавлять запрос всеми словоформами. В таком случае запрос пользователя скачка котиков будет преобразован во что-то типа "(скачка^2 OR скачать OR скачивать OR скачал) AND (котиков^2 OR котики OR котик OR котикам)". Выдача будет выглядеть так, как будто бы мы умеем по-настоящему работать с леммами. Содержимое инвертированного индекса менять не требуется!

Все гипотетические затраты процессора на переиндексацию благодаря простому подходу будут перенесены на этап query extension и на обработку такого расширенного запроса. Это сэкономит вам кучу времени разработки. Fail often, fail fast!

Запись и сегментирование индекса


В архитектуре Lucene инвертированный индекс нарезан на сегменты. Сегмент хранит свою часть документов и является неизменяемым. Для добавления документов мы собираем в RAM новые документы, делаем commit и в этот момент документы из RAM сохраняются в новый сегмент.

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

Сегменты могут обрабатывать запросы одновременно, поэтому сегмент является естественной единицей распараллеливания нагрузки. Сложность здесь возникает только в слиянии результатов из разных сегментов, так как нужно выполнять N-Way Merge потоков документов от каждого сегмента.

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

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

Шардирование


Существует два способа распараллеливания нагрузки на инвертированный индекс: по документам или по термам.

В первом случае каждый из N серверов хранит только часть документов, но является сам по себе полноценным мини-индексом, во втором хранит только часть термов для всех документов. Ответы шардов во втором случае требуют дополнительной нетривиальной обработки.
По документам По термам
Нагрузка на сеть Маленькая Большая
Хранение дополнительных аттрибутов для документа Просто Сложно
Disk-seek'ов для запроса из K слов на N шардах O(K*N) O(K)

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

Поскольку в инвертированном индексе перестроение сегментов является сложной операцией, лучше сразу начать использовать схемы типа Ring или Jump Consistent Hashing для снижения объемов перешардируемых документов при открытии нового шарда.

Многофазовые поиски и ранжирование


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

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

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

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

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

Первая фаза ранжирования


Метрика соответствия документа запросу может быть очень простой, например $BM25$, или чуть более сложной, например $BM25 * f(IMP)$, где $IMP$ статическое качество документа, а $f$ произвольное отображение с областью значений $[0; 1]$.

Ограничение на $f$ в этом случае произрастает из-за использованных оптимизаций в индексе типа BMW, которые не позволяют модифицировать $score(t, d)$ в большую сторону без изменения сохраненных блочных $UB$.

BM25 высчитывается в процессе работы с постинг-листами, а вот дополнительные члены типа $IMP$ должны лежать рядом с инвертированным индексом. Это первое существенное ограничение первой фазы поиска. Через функцию $score(q, d)$ в общем случае проходит слишком много документов, чтобы была возможность на каждый из них бегать куда-то во внешние системы за дополнительными атрибутами документа.

В веб-поиске роль члена $IMP$ обычно исполняет PageRank, рассчитываемый раз в N дней на больших кластерах MapReduce. Посчитанный PageRank записывается в быстрое KV-хранилище инвертированного индекса, такое как FastFields в случае Tantivy и используется при вычислении релевантности.

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

Вторая фаза ранжирования


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

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

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

Качество поиска


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

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

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

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

Далее пара метрик, с которых вам стоит начать. Они просто формулируется даже в терминах SQL-запроса, особенно если у вас что-то типа Clickhouse для хранения логов.

Success Rate

Самое первое и очевидное нашёл ли вообще пользователь у вас хоть что-нибудь в рамках сессии.
SQL-сниппет
with 1.959964 as zselect    t,    round(success_rt + z*z/(2*cnt_sess) - z*sqrt((success_rt*(1 - success_rt) + z*z/(4*cnt_sess))/cnt_sess)/(1 + z*z/cnt_sess), 5) as success_rate__lower,    round(success_rt, 5) as success_rate,    round(success_rt + z*z/(2*cnt_sess) + z*sqrt((success_rt*(1 - success_rt) + z*z/(4*cnt_sess))/cnt_sess)/(1 + z*z/cnt_sess), 5) as success_rate__upperfrom (    select        toDateTime(toDate(min_event_datetime)) as event_datetime,        $timeSeries as t,        count(*) as cnt_sess,        avg(success) as success_rt    from (        select            user_id,            session_id,            min(event_datetime) as min_event_datetime,            max(if($yourConditionForClickEvent, 1, 0)) as success        from            $table        where            $timeFilter and            $yourConditionForSearchEvent        group by            user_id,            session_id    )    group by        t,        event_datetime)order by      t


MAP@k

Хорошее введение в MAP@k, а также в несколько других learning-to-rank метрик есть на Хабре. Скорее всего первое, что вы посчитаете из серьезных метрик. Метрика характеризует насколько хороший у вас top-K документов, где K обычно берется равным количеству элементов на странице поисковой выдачи.
SQL-сниппет
select    $timeSeries as t,    avg(if(AP_10 is null, 0, AP_10)) as MAP_10from(    select        session_id,        min(event_datetime) as event_datetime    from (        select            session_id,            event_datetime        from            query_log        where            $timeFilter and            $yourConditionForSearchEvent    )    group by        session_id) searchleft join(    select        session_id,        sum(if(position_rank.1 <= 10, position_rank.2 / position_rank.1, 0))/10 as AP_10    from (        select            session_id,            groupArray(toUInt32(position + 1)) as position_array_unsorted,            arrayDistinct(position_array_unsorted) as position_array_distinct,            arraySort(position_array_distinct) as position_array,            arrayEnumerate(position_array) as rank_array,            arrayZip(position_array, rank_array) as position_rank_array,            arrayJoin(position_rank_array) as position_rank        from            query_log        where            $timeFilter and            $yourConditionForClickEvent        group by            session_id    )    group by        session_id) clickon    search.session_id = click.session_idgroup by    torder by    t


Вместо заключения: Google и их первый инвертированный индекс



Рис. 6: Вот что бывает, когда программистов заставляют рисовать схемы против их воли (воспроизведение оригинальной блок-схемы из Brin, 1998)

Студенты Сергей Брин и Ларри Пейдж создали первую версию Google и проиндексировали около 24 миллионов документов в 1998 году. Скачивание документов студенты реализовали на Python, всего они запускали по 3-4 процесса паука. Один паук объедал примерно по 50 документов в секунду. Полное заполнение базы занимало 9 дней, выкачивалось под сотню ГБ данных. Сам индекс исчислялся десятками ГБ.

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

Основа индекса файлы в формате Barrel. Barrel текстовый файл, хранящий четверки did, tid, offset, info, отсортированные по did, offset. В этой нотации did id документа, tid id терма, offset позиция терма в документе.

В оригинальной системе было 64 таких Barrel файла, каждый из которых отвечал за свой диапазон термов (tid). Новый документ получал новый did и соответствующие четверки дописывались в конец Barrel файлов.

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


Рис. 7: Пересортировка Barrel-файлов

Всё! Ещё раз мы пересортировываем одновременно 64 файла и получаем инвертированный индекс из прямого, так как теперь бинарным поиском можно искать уже по tid.

За форматом Barrel-файлов совершенно явно выглядывают уши MapReduce концепции, полноценно реализованной и задокументированной в работе J.Dean, 2004 позже.

О Google в общем доступе находится много вкусных материалов. Начать можно с оригинальной работы Brin, 1998 об архитектуре поиска, дальше потыкать в материалы Университета Нью-Джерси и шлифануть всё презентацией J.Dean о внутренней кухне первых версий индекса.

Представляете, приходите вы на работу, а вам говорят, что весь следующий год каждый месяц вам нужно будет ускорять код на 20%, чтобы дебит с кредитом сошлись. Так жил Google в начале нулевых годов. Разработчики в компании уже до того доигрались, что насильно переселяли файлы индекса на внешние цилиндры HDD у них линейная скорость вращения выше была и файлы считывались быстрее.

К счастью, в 2021 году такие оптимизации уже не особо нужны. HDD практически вытеснены из оперативной работы, а индекс, начиная с середины 2010-ых, целиком размещается в RAM.

Дополнительная литература:

Подробнее..

О локальном поиске замолвите слово

23.08.2020 18:21:00 | Автор: admin
В стародавние времена я работал айтишником в одной фирме и в какое-то время возникла задача поиска по локальному хранилищу документов. Искать желательно было не только по названию файла, но и по содержанию. Тогда ещё были популярны локальные поисковые механизмы типа архивариуса и даже от Яндекса был отдельностоящий поисковик. Но это были не корпоративные решения их нельзя было развернуть централизовано для совместного использования. Яндекс, честности ради начал делать что-то похожее, но потом забросил.
Но у всех этих решений не было того, что мне нужно:
Централизованная установка
Поисковая выдача с учётом прав доступа
Поиск по содержимому документа
Морфология
И я решил сделать своё.

Раскрою по пунктам, что я имею в виде для избегания разности толкований и недопониманий.
Централизованная установка клиент-серверное исполнение. У всех перечисленных выше решений одна фундаментальная проблема каждый пользователь делает свой локальный поисковый индекс, что в случае больших объёмов хранилищ затягивает индексирование, растёт профиль пользователя на машине и вообще неудобно в случае прихода нового сотрудника или переезда на новую машину.
Поисковая выдача в учётом прав тут всё просто выдача должна соответствовать правам сотрудника на файловый ресурс. А то получится, что даже если у сотрудника нет прав на ресурс, но он может всё почитать из поискового кеша. Неудобненько получится, согласитесь?
Поиск по содержимому документа поиск по тексту документа, тут всё очевидно, как мне кажется и разночтений быть не может.
Морфология ещё проще. Указали в запросе нож и получили, как нож, так и ножи, ножевой и ножик. Желательно, чтобы это работало для русского и английского языков.
С постановкой задачи определились, можно переходить к реализации.
В качестве именно поисковой машины я выбрал систему Sphinx, а язык разработки интерфейса C# и .net и в итоге проект получил название Vidocq (Видок) по имени французского сыщики Ну типа найдёт всё и вот это вот всё.
Архитектурно приложение выглядит следующим образом:
Поисковый робот рекурсивно обходит файловый ресурс и обрабатывает файлы по заданному списку расширений. Обработка заключается в получении содержимого файла, сжатию текста из текста убираются кавычки, запятые, лишние пробелы и прочее, дальше содержимое помещается в базу (MS SQL), делается отметка о дате помещения и робот идёт дальше.
Индексатор Сфинса работает уже непосредственно с полученной базой, формируя свой индекс и возвращая в качестве ответа указатель на найденный файл и сниппет найденного фрагмента текста.
На C# была разработана форма, которая общалась со Сфинксом через MySQL-коннектор. Сфинкс отдаёт массив файлов в соответствии с запросом, дальше массив фильтруется на право доступа того пользователя, который осуществляет поиск, выдача форматируется и показывается пользователю.
О файле нам понадобится хранить следующую информацию:
Id файла
Имя файла
Путь к файлу
Содержимое файла
Расширение
Дата добавления в базу
Это всё делается одной таблицей и в неё всё будет складывать поисковый робот. Дата добавления необходима для того, чтобы когда робот в следующий обход сравнивал дату изменения файла с датой помещения в базу и если даты различаются, то обновить информацию о файле.
Дальше настраивать саму поисковую машину. Я не буду описывать весь конфиг, он будет доступен в архиве проекта, но освещу лишь основные моменты.
Основной запрос, формирующий базу
source documents: documents_base
{
sql_query = \
select \
DocumentId as 'Id', \
DocumentPath as 'Path', \
DocumentTitle as 'Title', \
DocumentExtention as 'Extension', \
DocumentContent as 'Content' \
from \
VidocqDocs
}

Настройка морфологии через лемматайзер.
index documents
{
source = documents
path = D:/work/VidocqSearcher/Sphinx/data/index
morphology = lemmatize_ru_all, lemmatize_en_all
}

После этого на базу можно натравливать индексатор и проверять работу.
d:\work\VidocqSearcher\Sphinx\bin\indexer.exe documents --config D:\work\VidocqSearcher\Sphinx\bin\main.conf rotate
Тут путь к индексатору дальше имя индекса в который поместить обработанное, путь к конфигу и Флаг rotate означает, что индексация будет выполняться наживую, т.е. при работающей службе поиска. После завершения индексации индекс будет заменен на обновлённый.
Проверяем работу в консоли. В качестве интерфейса можно использовать клиента MySQL, взятого, например, из комплекта веб-сервера.
mysql -h 127.0.0.1 -P 9306
после этого запрос select id from documets; должен вернуть список проиндексированных документов, если, конечно, вы перед этим запустили саму службу Sphinx и всё сделали правильно.
Хорошо, консоль это прекрасно, но мы же не будем заставлять пользователей вбивать руками команды, верно?
Я набросал вот такую вот форму

И вот с результатами поиска

При щелчке по конкретному результату открывается документ.
Как реализовано.
using MySql.Data.MySqlClient;
string connectionString = "Server=127.0.0.1;Port=9306";
var query = "select id, title, extension, path, snippet(content, '" + textBoxSearch.Text.Trim() + "', 'query_mode=1') as snippet from documents " +
"where ";
if (checkBoxTitle.IsChecked == true && checkBoxContent.IsChecked == true)
{
query += "match ('@(title,content)" + textBoxSearch.Text.Trim() + "')";
}

if (checkBoxTitle.IsChecked == false && checkBoxContent.IsChecked == true)
{
query += "match ('@content" + textBoxSearch.Text.Trim() + "')";
}

if (checkBoxTitle.IsChecked == true && checkBoxContent.IsChecked == false)
{
query += "match ('@title" + textBoxSearch.Text.Trim() + "')";
}



if (checkBoxWord.IsChecked == true && checkBoxText.IsChecked == true)
{
query += "and extension in ('.docx', '.doc', '.txt');";
}
if (checkBoxWord.IsChecked == true && checkBoxText.IsChecked == false)
{
query += "and extension in ('.docx', '.doc');";
}

if (checkBoxWord.IsChecked == false && checkBoxText.IsChecked == true)
{
query += "and extension in ('.txt');";
}

Да, тут быдлокод, но это MVP.
Собственно, тут формируется запрос к Сфинксу в зависимости от выставленных чексбоксов. Чекбоксы указывают на тип файлов в которых искать и область поиска.
Дальше запрос уходит в Сфинкса, а потом разбирается полученный результат.
using (var command = new MySqlCommand(query, connection))
{
connection.Open();

using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetUInt16("id");
var title = reader.GetString("title");
var path = reader.GetString("path");
var extension = reader.GetString("extension");
var snippet = reader.GetString("snippet");
bool isFileExist = File.Exists(path);
if (isFileExist == true)
{
System.Windows.Controls.RichTextBox textBlock = new RichTextBox();
textBlock.IsReadOnly = true;
string xName = "id" + id.ToString();
textBlock.Name = xName;
textBlock.Tag = path;
textBlock.GotFocus += new System.Windows.RoutedEventHandler(ShowClickHello);
snippet = System.Text.RegularExpressions.Regex.Replace(snippet, "<.*?>", String.Empty);
Paragraph paragraph = new Paragraph();
paragraph.Inlines.Add(new Bold(new Run(path + "\r\n")));
paragraph.Inlines.Add(new Run(snippet));
textBlock.Document = new FlowDocument(paragraph);
StackPanelResult.Children.Add(textBlock);
}
else
{
counteraccess--;
}
}
}
}

На этом же этапе формируется выдача. Каждый элемент выдачи richtextbox с событием открытия документа на клик. Элементы помещаются на StackPanel и перед этим идёт проверка доступности файла пользователю. Таким образом, в выдачу не попадёт файл, недоступный пользователю.
Преимущества такого решения:
Индексация происходит централизованно
Чёткая выдача с учётом прав доступа
Настраиваемый поиск по типам документов
Разумеется, для полноценной работы такого решения в компании должно быть соответствующим образом организован файловый архив. В идеале должны быть настроены перемещаемые профиля пользователей и прочее. И да, я знаю про наличие SharePoint, Windows Search и скорее всего ещё нескольких решений. Дальше можно бесконечно долго дискутировать о выборе платформы разработки, поисковой машине Sphinx, Manticore или Elastic и так далее. Но мне было интересно решить задачу тем инструментарием в котором я немного разбираюсь. Сейчас оно работает в режиме MVP, но я развиваю его.
Но в любом случае я готов выслушать ваши предложения о том, какие моменты можно или улучшить или переделать на корню.
Подробнее..

Перевод Machine learning в анализе логов Netflix

21.09.2020 16:09:27 | Автор: admin

Представьте лог на 2,5 гигабайта после неудачной сборки. Это три миллиона строк. Вы ищете баг или регрессию, которая обнаруживается на миллионной строке. Вероятно, найти одну такую строку вручную просто невозможно. Один из вариантов diff между последней успешной и упавшей сборкой в надежде на то, что баг пишет в журналы необычные строки. Решение Netflix быстрее и точнее LogReduce под катом.

Netflix и строка в стоге лога


Стандартный md5 diff работает быстро, но выводит не меньше сотни тысяч строк-кандидатов на просмотр, поскольку показывает различия строк. Разновидность logreduce нечёткий diff с применением поиска k ближайших соседей находит около 40 000 кандидатов, но отнимает один час. Решение ниже находит 20 000 строк-кандидатов за 20 минут. Благодаря волшебству открытого ПО это всего около сотни строк кода на Python.

Решение комбинация векторных представлений слов, которые кодируют семантическую информацию слов и предложений, и хеша с учетом местоположения (LSH Local Sensitive Hash), который эффективно распределяет приблизительно близкие элементы в одни группы и далёкие элементы в другие группы. Комбинирование векторных представлений слов и LSH великолепная идея, появившаяся менее десяти лет назад.
Примечание: мы выполняли Tensorflow 2.2 на CPU и с немедленным выполнением для трансферного обучения и scikit-learn NearestNeighbor для k ближайших соседей. Существуют сложные приближенные реализации ближайших соседей, что были бы лучше для решения проблемы ближайших соседей на основе модели.

Векторное представление слов: что это и зачем?


Сборка мешка слов с k категориями (k-hot encoding, обобщение унитарного кодирования) типичная (и полезная) отправная точка дедупликации, поиска и проблем сходства неструктурированного и полуструктурированного текста. Такой тип кодирования мешка со словами выглядит как словарь с отдельными словами и их количеством. Пример с предложением log in error, check log.

{"log": 2, "in": 1, "error": 1, "check": 1}


Такое кодирование также представляется вектором, где индекс соответствует слову, а значение количеству слов. Ниже показывается фраза log in error, check log" в виде вектора, где первая запись зарезервирована для подсчета слов log, вторая для подсчета слов in и так далее:

[2, 1, 1, 1, 0, 0, 0, 0, 0, ...]

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

Посмотрим на словарь и векторные представления фразы problem authentificating. Слова, соответствующие первым пяти векторным записям, вообще не появляются в новом предложении.

{"problem": 1, "authenticating": 1}

Получается:

[0, 0, 0, 0, 1, 1, 0, 0, 0, ...]

Предложения problem authentificating и log in error, check log семантически похожи. То есть они по существу одно и то же, но лексически настолько различны, насколько это возможно. У них нет общих слов. В разрезе нечёткого diff мы могли бы сказать, что они слишком похожи, чтобы выделять их, но кодирование md5 и документ, обработанный k-hot с kNN этого не поддерживает.

Сокращение размерности использует линейную алгебру или искусственные нейронные сети для размещения семантически похожих слов, предложений или строк лога рядом друг с другом в новом векторном пространстве. Применяются векторные представления. В нашем примере log in error, check log может иметь пятимерный вектор для представления:

[0.1, 0.3, -0.5, -0.7, 0.2]

Фраза problem authentificating может быть

[0.1, 0.35, -0.5, -0.7, 0.2]


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

На самом деле вы заменили бы тысячи или более размерностей словаря всего лишь представлением в 100 размерностей, богатыми информацией (а не пятью). Современные подходы к снижению размерности включают разложение по сингулярным значениям матрицы совместной встречаемости слов (GloVe) и специализированные нейронные сети (word2vec, BERT, ELMo).

А как насчет кластеризации? Вернёмся к логу сборки


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

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

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

Вектор представления фразы log in error, check error может быть сопоставлен с двоичным числом 01. Затем 01 представляет кластер. Вектор problem authentificating с большой вероятностью также может быть отображен в 01. Так LSH обеспечивает нечёткое сравнение и решает обратную задачу нечёткое различие. Ранние приложения LSH были над многомерными векторными пространствами из набора слов. Мы не смогли придумать ни одной причины, по которой он не работал бы с пространствами векторного представления слов. Есть признаки, что другие думали так же.



Выше изображено использование LSH при размещении символов в той же группе, но в перевернутом виде.

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

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

Несколько примеров


Любимый пример семантического diff. 6892 строк превратились в 3.



Другой пример: эта сборка записала 6044 строки, но в отчете осталась 171. Основная проблема всплыла почти сразу на строке 4036.



Конечно, быстрее проанализировать 171 строку, чем 6044. Но как вообще мы получили такие большие логи сборок? Некоторые из тысяч задач сборки это стресс-тесты для потребительской электроники выполняются в режиме трассировки. Трудно работать с таким объёмом данных без предварительной обработки.



Коэффициент сжатия: 91366/455 = 205,3.

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

Заключение


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

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

Если у вас есть какие-либо вопросы о возможностях в Netflix, обращайтесь к авторам в LinkedIn: Stanislav Kirdey, William High

А как вы решаете проблему поиска в логах?

image

Узнайте подробности, как получить востребованную профессию с нуля или Level Up по навыкам и зарплате, пройдя онлайн-курсы SkillFactory:



Подробнее..

Перевод Как просто и быстро искать данные с помощью Whale

22.10.2020 18:09:59 | Автор: admin

В этом материале рассказывается о простейшем и быстром инструменте обнаружения данных, работу которого вы видите на КДПВ. Интересно, что whale создан таким образом, чтобы размещаться на удаленном git-сервере. Подробности под катом.

Как инструмент обнаружения данных Airbnb изменил мою жизнь


В моей карьере мне посчастливилось работать над некоторыми забавными проблемами: я изучал математику потоков во время получения степени в MIT, работал над инкрементальными моделями и с проектом с открытым исходным кодом pylift в Wayfair, а также внедрял новые модели таргетинга на домашнюю страницу и улучшения CUPED в Airbnb. Но вся эта работа никогда не была гламурной на самом деле я часто тратил большую часть своего времени на поиск, изучение и проверку данных. Хотя это было постоянным состоянием на работе, мне не приходило в голову, что это проблема, пока я не попал в Airbnb, где она была решена с помощью инструмента обнаружения данных Dataportal.

Где я могу найти {{data}}? Dataportal.
Что означает эта колонка? Dataportal.
Как дела у {{metric}} сегодня? Dataportal.
В чем смысл жизни? В Dataportal, вероятно.

Ладно, вы представили картину. Найти данные и понять, что они означают, как они были созданы и как их использовать все это занимает всего несколько минут, а не часов. Я мог бы тратить свое время на то, чтобы делать простые выводы, или на новые алгоритмы, ( или ответы на случайные вопросы о данных), а не рыться в заметках, писать повторяющиеся запросы SQL и упоминать коллег в Slack, чтобы попытаться воссоздать контекст, который уже был у кого-то другого.

А в чем проблема?


Я понял, что большинство моих друзей не имели доступа к такому инструменту. Немногие компании хотят выделять огромные ресурсы на создание и поддержание такого инструмента-платформы, как Dataportal. И хотя существует несколько решений с открытым исходным кодом, они, как правило, разработаны для масштабирования, что затрудняет настройку и обслуживание без посвященного в эту работу инженера DevOps. Поэтому я решил создать нечто новое.

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




И да, под простым до глупости подразумеваю простой до глупости. У whale только два компонента:

  1. Библиотека Python, собирающая метаданные и форматирующая их в MarkDown.
  2. Интерфейс командной строки на Rust для поиска по этим данным.

С точки зрения внутренней инфраструктуры для обслуживания есть только множество текстовых файлов и обновляющая текст программа. Вот и все, поэтому размещение на git-сервере, таком как Github, тривиально. Никакого нового языка запросов, который пришлось бы изучать, никакой инфраструктуры управления, никаких резервных копий. Git известен всем, так что синхронизация и совместная работа прилагаются бесплатно. Давайте подробнее рассмотрим функциональность Whale v1.0.

Полнофункциональный графический интерфейс на основе git


Whale создан, чтобы плыть в океане удаленного сервера git. Он очень легко настраивается: определите некоторые соединения, скопируйте скрипт Github Actions (или напишите его для выбранной платформы CI/CD) и вы сразу же получите веб-инструмент обнаружения данных. Вы сможете искать, просматривать, документировать и делиться своими таблицами непосредственно на Github.


Пример сгенерированной с помощью Github Actions таблицы-заглушки. Полную работающую демонстрацию смотрите в этом разделе.

Молниеносно быстрый поиск из CLI по вашему хранилищу


Whale живет и дышит в командной строке, предоставляя функциональный, миллисекундный поиск по вашим таблицам. Даже при миллионах таблиц нам удалось сделать whale невероятно производительным, используя некоторые хитроумные механизмы кэширования, а также перестроив бекенд на Rust. Вы не заметите никакой задержки поиска [привет, Google DS].


Демонстрация whale, поиск по миллиону таблиц.

Автоматическое вычисление метрик [в бете]


Одна из моих наименее любимых вещей как ученого-исследователя данных запуск одних и тех же запросов снова и снова просто для проверки качества используемых данных. Whale поддерживает возможность определения метрик в простом SQL, который будут выполняться по расписанию вместе с вашими конвейерами очистки метаданных. Определите блок metrics в формате YAML внутри таблицы заглушки, и Whale будет автоматически выполняться по расписанию и запускать вложенные в metrics запросы.

```metricsmetric-name:  sql: |    select count(*) from table```


В сочетании с Github такой подход означает, что whale может служить легким центральным источником истины для определений метрик. Whale даже сохраняет значения вместе с меткой времени в каталоге~/. whale/metrics, если вы хотите сделать какой-то график или более глубокое исследование.

Будущее


Поговорив с пользователями наших предрелизных версий whale, мы поняли, что людям нужна более широкая функциональность. Почему именно инструмент табличного поиска? Почему не инструмент поиска метрик? Почему не мониторинга? Почему не инструмент выполнения запросов SQL? Хотя whale v1 изначально задумывался как простой инструмент-компаньон CLI Dataportal/Amundsen, он уже превратился в полнофункциональную автономную платформу, и мы надеемся, что он станет неотъемлемой частью инструментария дата-сайентиста.

Если есть что-то, что вы хотите видеть в процессе разработки, присоединяйтесь к нашему сообществу Slack, откройте Issues на Github, или даже обращайтесь непосредственно в LinkedIn. У нас уже есть целый ряд интересных функций шаблоны Jinja, закладки, фильтры поиска, оповещения Slack, интеграция с Jupyter, даже панель CLI для метрик но мы будем рады вашему вкладу.

Заключение


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

image

А по промокоду HABR, можно получить дополнительные 10% к скидке указанной на баннере.



Рекомендуемые статьи


Подробнее..

Почему я не могу найти Яндекс.Такси через системный поиск на iPhone?

05.01.2021 18:18:32 | Автор: admin

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

В последнее время Яндекс изо всех сил пытается испортить свои мобильные приложения, превращая их в суперапп, будь это слово неладно. И одно из двух приложений компании, для которых такое направление развития наиболее заметно это такси.

Не так давно его переименовали в Яндекс Go, допихнув заодно внутрь Еду, Лавку, общественный транспорт, кучу рекламы. И здесь-то я вероломно, без объявления войны наткнулся на проблему, которая в конечном счёте послужила идеей для написания сией микростатьи.

У меня на iPhone довольно много разных приложений, и я привык запускать те, что не размещены на первой же странице, через системный поиск Spotlight тот, что на домашнем экране iOS. Беда в том, что с упомянутым переименованием Яндексовского приложения из его названия исчезло собственно главное ключевое слово такси. Найти Яндекс Go по нему теперь стало невозможно. Затрудняюсь предположить, насколько от этого могли пострадать статистика запусков или доходы приложения, но как минимум UX точно оказался в проигрыше, причём довольно глупом. К слову, точно так же вы не сможете найти такси Maxim по ключевому слову максим, а Delivery Club не ищется по запросу еда.

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

Ключевые слова для Spotlight легко добавляются, если вы уже поддерживаете Handoff, то есть бесшовное переключение юзера между разными своими Apple-устройствами, или Siri Shortcuts. Достаточно лишь проставить свойство keywords для объекта NSUserActivity, с которым вы и так работаете.

let activity = NSUserActivity(activityType: typeID)activity.keywords = ["слово", "или даже ключевая фраза"]

Если же с Handoff и Siri вам по каким-то причинам не по пути либо хочется поддержать Spotlight на более серьёзном уровне, используйте CoreSpotlight.

import CoreSpotlightlet attributes = CSSearchableItemAttributeSet(contentType: .application)attributes.title = "Заказать пиццу"attributes.contentDescription = "Доставим в течение получаса"attributes.thumbnailData = image.pngData()attributes.keywords = ["еда", "закуски", "кушать"]let searchableItem = CSSearchableItem(uniqueIdentifier: "pizza", domainIdentifier: "readyMeal", attributeSet: attributes)CSSearchableIndex.default().indexSearchableItems([searchableItem]) { _ -> Void in }

Если всё сделали правильно, при вводе одного из переданных нами операционной системе ключевых слов получим примерно следующий результат...

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

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

Спасибо за внимание!

Подробнее..

Ресурсы для поиска удаленной работы для нетехнарей

29.12.2020 00:19:38 | Автор: admin

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

Тем, у кого уровень английского "London is the capital of Great Britain", я советую идти на всем известные российские ресурсы и искать там по фильтру "удаленная работа". А тем, чей уровень английского позволяет, кто хочет получать з/п в долларах и готов заморочиться с оформлением ИП или самозанятости, можно спокойно предлагать поискать иностранную компанию. Для технарей этот список тоже будет актуален.

Это, конечно, не исчерпывающий список, и будет круто, если вы дополните его в комментариях. Удачи в поиске!

  • WeWorkRemotely
    Один из самых старых сайтов с удаленными вакансиями от создателей Basecamp
    Фильтры: Copywriting, Business & Management, Customer Support, Finance and Legal, Sales and Marketing

  • AngleList
    Американские и канадские стартапы, здесь я нашла свою текущую работу
    Фильтры: Operations, Marketing, Sales, Management, Other

  • F6s
    Европейские стартапы
    Фильтры: Operations, Marketing, Financial, Accounting

  • Workable
    Сайт агрегатор, публикации напрямую от компаний

  • NODESK
    Фильтр: "non-tech"

  • Revolution Remote
    Только удаленные вакансии поддержки Support, Service, Helpdesk, Tech Support

  • People-First Jobs
    Публикуют только вакансии от компаний, которые ценят своих сотрудников
    Фильтры: Business management, Copywriting, Customer success & support, Design, Finance & legal, Operations, Product, Project management, Sales & marketing, Others

  • REMOTIVE
    фильтр: All Other

  • RemoteOK
    Фильтр: Non-tech

  • EU Remote Jobs
    Фильтры: Customer Support, Human Resources, Legal, Marketing, Sales

  • Europe Remotely
    Фильтры: Marketing & Sales, Customer Support

  • Workingтоmads
    Фильтры: Administration, Human Resources, Education, Legal, Sales, Writing, Sales filters

  • Skip the Drive
    Фильтры: Administrative, Consulting, Editor, Entry Level, Human Resources

  • Twitter
    Не самый очевидный ресурс, но именно в Твиттер дублируют вакансии как сами компании, так и сайты агрегаторы.
    Искать по "должность" или "скилл" + remote job

Фото: Jen Theodore on Unsplash

Подробнее..

Как сделать поиск по документам, накопленным почти за 100 лет. Опыт НПО Энергомаш и ABBYY

29.07.2020 14:20:10 | Автор: admin
Многие знают, что ABBYY занимается обработкой и извлечением данных из разных документов. Но у наших продуктов есть и другие интересные возможности. В частности, с помощью решения ABBYY Intelligent Search можно быстро и удобно искать информацию по смыслу в электронных документах из корпоративных систем. Этим уже пользуются крупные российские компании, например, производитель ракетных двигателей АО НПО Энергомаш.

Многолетняя практика показывает, что время вывода космических двигателей на рынок от момента начала работ составляет от 5 до 7 лет. В то же время для удержания лидирующих позиций необходимо сокращать сроки разработки и изготовления до 3 4 лет. Кроме того, усиление конкуренции привело к необходимости существенного снижения стоимости выпускаемых двигателей на 30 50%.

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

За 90 лет работы НПО Энергомаш накопил вековой объем документов (как бумажных, так и электронных) с ценной информацией о наработках испытателей и конструкторов. Большая часть документов уже хранится в информационных системах компании (ИС). Согласно исследованию IDC, в среднем сотрудники крупных организаций пользуются 5-6 внутренними ИС. Около 36% времени в среднем уходит на поиск информации в масштабах крупной компании это тысячи рабочих часов в день.

Сегодня мы расскажем, как помогли НПО Энергомаш создать корпоративную интеллектуальную информационно-поисковую систему (КИИПС) на базе ABBYY Intelligent Search такую же удобную и быструю, как популярные поисковики.

Чем занимается Энергомаш и при чем тут Гагарин


Со дня основания, 15 мая 1929 года, Энергомаш изготовил более 12 тысяч двигателей для ракет-носителей не только российского, но и зарубежного производства. На этих моторах был запущен первый искусственный спутник Земли, отправился в космос Восток-1 с первым космонавтом Юрием Гагариным на борту, совершил полет космоплан Буран и до сих пор осуществляются пуски американских ракет-носителей Atlas и Antares. Например, 26 марта 2020 года ракета-носитель Atlas V, оснащенная двигателями российского производства, вывела на орбиту американский военный спутник стратегической системы связи. В первом полугодии 2020 года двигатели разработки Энергомаш успешно отработали в 11 космических пусках, что составляет 24,4% от всех пусков в мире.

Сегодня Энергомаш входит в госкорпорацию Роскосмос и возглавляет интегрированную структуру ракетного двигателестроения, в которую входят ведущие предприятия этой отрасли.

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

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

Зачем понадобился поиск по вселенной Энергомаша


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

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

В рамках проекта решалось две задачи:

1). Упростить для конструкторов и инженеров поиск полезной информации в документах прошлых лет.

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

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

Под спойлером рассказываем подробнее, как раньше был устроен этот процесс.
В каждой из 7 информационных систем был встроенный поисковый механизм со своими правилами. Например, где-то нужную информацию искали с помощью тегов, которые сотрудники проставляли к документам, где-то по ключевым словам, то есть тем, которые встречаются в тексте. Соответственно, качество поиска в разных ИС отличалось, а сотрудники сталкивались с некоторыми трудностями:

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

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

В результате:

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

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

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

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

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

Почему это важно? Через 7 лет более половины всех данных в мире будут храниться в корпоративных системах, следует из отчета Seagate и IDC Data age. Чтобы необходимая информация всегда была под рукой, ее надо быстро находить. Так, по данным исследования IDC и ABBYY Рынок искусственного интеллекта в России, представители ИТ (48%) и бизнес-подразделений (33%) видят большие возможности в применении ИИ для корпоративного поиска и классификации документов в ближайшие два года.

Чтобы справиться с этими задачами, компании понадобился удобный сквозной поиск по многочисленным ИС. Энергомаш рассматривал несколько поисковых систем, но в итоге решил попробовать ABBYY Intelligent Search. На выбор повлияло, во-первых, наличие технологий обработки естественного языка, которые позволяют находить документы, релевантные поисковым запросам по смыслу, а не только по ключевым словам. Во-вторых, возможность разграничивать права доступа пользователей к результатам поиска. Подробнее об этом мы расскажем чуть позже, а сейчас о том, как мы стартовали.

Первый выход в поиск


Энергомаш решил проверить работу интеллектуального поиска на 3 тысячах документов из информационной базы данных (ИБД) исследовательских, конструкторских и расчетных работ.
Для этого ABBYY разработала прототип коннектора к ИБД, который связал ABBYY Intelligent Search c базой документов. Коннектор это java-программа, которая используется для загрузки документов в индекс. Как это работает?

1). Сначала строим полнотекстовый поисковый индекс


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

image


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

  • название ИС, где хранится файл,
  • дату последней модификации документа,
  • версию документа в источнике,
  • формат документа,
  • коды языков, на которых составлен документ,
  • путь к документу в ИС,
  • дату последней индексации документа
  • и др.

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

Создавать поисковый индекс помогает встроенный в ABBYY Intelligent Search краулер (поисковый робот). Он через равные промежутки времени опрашивает коннекторы, проверяет, появились ли в ИС новые документы, какие документы удалены, как изменились права доступа к документам. Соответственно, с заданной периодичностью индекс обновляется.

Индексируются не только текстовые документы, но и файлы в графическом формате. Например, это могут быть скан-копии чертежей в JPEG или PDF без текстового слоя. При работе с изображениями, поисковое решение сначала автоматически распознает текст и добавляет его в поисковый индекс.

Кроме того, система умеет обрабатывать архивные файлы ZIP, RAR, TAR при условии, что они не защищены паролем. Архивы распаковываются, изображения из них распознаются, текст индексируется.



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

2). Затем применяем технологии обработки естественного языка


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

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



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

Приведем еще один пример:

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

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

Итоги первого запуска


Чтобы оценить работу интеллектуального поисковика, специалисты Энергомаш выполнили примерно по 30 запросов по документам ИБД с помощью встроенной в ИБД поисковой системы и с помощью ABBYY Intelligent Search. Затем сравнили результаты поисковой выдачи: какие документы удалось найти обеим системам, какие фразы подсвечивались в сниппетах. В итоге, встроенный в ИБД поиск не выдал результатов по некоторым запросам, так как способен обнаружить только ключевые, а не близкие по значению слова. ABBYY Intelligent Search выдал релевантные по всем запросам документы.

Что касается скорости, то при соблюдении требований к аппаратной платформе поисковый отклик не превышал доли секунды, как у популярных поисковиков. На самые сложные запросы уходило максимум до 3 секунд.

После успешного пилотного проекта Энергомаш принял решение использовать решение ABBYY Intelligent Search в основе Корпоративной интеллектуальной информационно-поисковой системы.

Поехали дальше


Энергомаш подключил к поиску 7 корпоративных источников: систему электронного документооборота LanDocs, файловое хранилище, ИБД, систему поддержки жизненного цикла изделия TeamCenter, систему управления ресурсами Галактика ERP и AMM, информационную систему управления проектами. Для каждой информационной системы создан отдельный индекс. Это делает поисковую систему гибкой в администрировании и дает возможность заново строить индекс по каждой системе в отдельности, задавая новые условия. Доступ в Систему корпоративного поиска организован через внутренний портал предприятия на главной странице.

Основные модули системы корпоративного поиска:

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

Система корпоративного поиска запущена в промышленную эксплуатацию с 1 июля 2020 года. На момент запуска было проиндексировано 500 тысяч документов. Ожидается, что к концу года при активном использовании системы и подключении новых информационных источников количество документов в индексе достигнет более 1 миллиона.

Как обеспечить безопасность


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

1). Локальное хранение информации


Поисковое решение ABBYY развернуто на отдельном сервере во внутреннем контуре НПО Энергомаш. Там хранятся все поисковые индексы и их резервные копии на случай потерь и их настройки.

2). Ролевая модель информационной системы


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

Планы на будущее


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

Упомянем и о планах на будущее:

  • В будущем к системе корпоративного поиска планируется подключать информационные источники других предприятий, входящих в структуру Энергомаша. В этом случае поисковый индекс может расшириться до 2 млн документов.
  • Одна из задач, связанная с развитием проекта, это регулярное повышение качества поисковой выдачи. Для этого разрабатывается административная консоль, которая позволит с помощью веб-интерфейса анализировать статистику поиска. Например, отбирать запросы пользователей, которые не увенчались успехом: если мы видим, что по какому-то запросу пользователи не находят нужного документа, им можно помочь его найти. Например, за конкретным поисковым запросом можно закрепить необходимые ссылки на документы, которые могут быть никак не связаны с запросом тематически. Разумеется, анализ поисковой выдачи станет востребован в полной мере, когда к системе подключится большое количество пользователей и появится статистика использования поисковой системы.
  • Энергомаш также планирует исследовать возможность построения сложных аналитических отчетов с использованием функции поиска.



А на ваш взгляд, какие еще задачи можно решить с помощью корпоративного поиска?
Подробнее..

Из песочницы Технология видео поиска Video Color

29.08.2020 14:19:54 | Автор: admin

Немного о поиске


Когда мы говорим о поиске, то сразу представляем себе поисковую систему Google с формой для ввода текстовой строки и многие сотни результатов ссылок на найденные страницы. Однако задумаемся о предмете нашего поиска.

Что мы ищем?


  • Текст
  • Документы
  • HTML странички
  • Изображения
  • Аудио
  • Видео
  • Двоичные файлы

Для некоторых видов данных существуют специализированные поисковые системы. Например, существуют сайты специализирующиеся на поиске DLL файлов.

Поиск видео




Давайте рассмотрим поиск видео информации. Каким образом можно это сделать? Чисто теоретически?

  • По тексту
  • По изображению
  • По короткому видео фрагменту
  • По короткому аудио фрагменту

Текущее положение дел


Поисковики


  • Google
  • Microsoft
  • Yandex

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

image

Недостатки современных поисковых систем


К сожалению, все они страдают от следующих проблем:

  • Не всегда есть точное название найденного видео. Вместо этого может быть правильно указан актёр и представлены другие изображения с ним.
  • Нет точного указания положения в видео. А это весьма полезная информация.

image

Да, это так. Попробуйте сами и Вы убедитесь, что я прав. Поисковым системам свойственна некоторая неопределённость. Посмотрите скриншот представленный выше, тот, на котором изображён Том Хэнкс. Нет ни названия фильма, ни позиции, в которой он сделан.

image

Постановка задачи


Перед тем как приступить к решению задачи, давайте попытаемся её описать. Итак, что мы хотим?

Желаемая скорость выполнения запроса


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

А каков объём данных?


Давайте прикинем в уме.

Количество видео


Согласно базе данных по кинематографу IMDb, всего былоснятооколо 2,6 миллионов кинолент, включая отдельные эпизоды сериалов, мультфильмы и короткометражки. (Информация на 13 ноября 2018 года).

Для начала ограничимся круглым числом 1 миллион видео. Понятно, что мы даже не пытаемся коснуться YouTube и других аналогичных сервисов, где объём видео в разы больше. И главное, этот снежный ком будет только расти.

Количество кадров


Некоторые фильмы или эпизоды сериалов довольно коротки. Есть по 15-20 минут. С другой стороны, есть немало фильмов продолжительностью до 2 часов и более. Не мудрствуя лукаво, примем среднюю продолжительность видео равной 1 часу.

Большое количество фильмов сняты с частотой 24 кадра в секунду, но есть и более скоростные. В наше время каждый может снять свой фильм, а частота кадров в нём может быть и 60 и 100 и 200 FPS и выше. Всё зависит от видеокамеры, фотоаппарата, экшен-камеры, смартфона, камеры видео наблюдения и т.д. (нужное подчеркнуть). Всё в наших руках. Но, давайте примем в первом приближении частоту кадров среднестатистического видео равной 30 FPS.

В этом случае в среднестатистическом видео будет:

30 FPS * 3600 сек = 108 000 кадров

Округляя получим, что в среднестатистическом видео порядка 100 000 кадров.

Объем данных


Каков объём хранения информации об одном кадре? Очевидно, что это значение зависит от алгоритма сравнения кадров в нашей базе данных с заданным образцом. Мы используем два алгоритма для сравнения данных. В одном из них на кадр требуется около 30 байт, в другом около 10 байт. Возьмём среднее 20 байт.

Значит для хранения информации о 1 миллионе видео необходимо

1 000 000 видео * 100 000 кадров * 20 байт = 2 000 000 000 000 байт

Проще говоря, нам потребуется около 2 Тбайт для того чтобы как то описать все наши кадры. Что, вообще говоря, не так уж и плохо, ведь этот объём информации может уместиться на современном HDD или SSD диске. С другой стороны, эту информацию следует как то упорядочить, в противном случае даже для простого чтения 2 Тбайт понадобится ума времени, а мы ведь договорились, что пользователь не будет ждать более 10 секунд.

Даже если считывать информацию с диска со скоростью 500 Мбайт/сек нам понадобиться 2000 секунд, т.е. более получаса!

А сколько серверов нам понадобиться для поиска за указанное время?


Если предположить, что мы храним информацию равномерно на нескольких серверах, то, в этом случае, объём обрабатываемой информации для выполнения одного поискового запроса уменьшается. Например, если у нас 10 серверов, по на каждом из них потребуется обработать не 2 Тбайта информации, а только 200 Гбайт. Или если у нас 100 серверов, то потребуется обработать не 2 Тбайта, а 20 Гбайт информации. В принципе, указанного количества должно хватить для функционирования такой поисковой системы.

Сколько запросов в секунду сможет переварить такая система?


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

Что было сделано


Сначала нами был реализован поиск по видео фрагментам. Однако вскоре был реализован поиск по изображениям.

История


1 июля 2019


В этот день была выпущена первая версия пакета VideoColor. Она включала в себя три части:

  • Manager (индексирование исходного видео)
  • Server (серверная часть, которая принимает запросы и ищет совпадение в базе индексов)
  • Client (клиентское приложение, которое позволяет проигрывать AVI файлы и отсылать поисковые запросы серверу).

Март 2020


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

14 апрель 2020


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

23 июня 2020


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

Поиск по видео фрагментам


Основная идея




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



Получая видео фрагмент для идентификации построим эти графики и для него. Сравним полученные графики с уже имеющимися. Разумеется, сравнивать придётся по всей длине каждого исходного фильма. Если разница между графиками в конкретной точке меньше определённого значения, то считаем, что задача решена.

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

Плюсы


  • Относительно небольшой размер индексов. Один час видео в проиндексированном виде занимает около 1 МБ. Таким образом, 1000 фильмов, каждый продолжительностью около 2 часов, в проиндексированном виде займут около 2 ГБ.
  • Достаточно точный поиск. Даже если видео пережали несколько раз, если оно визуально выглядит удовлетворительным, то фрагмент скорее всего будет найден.
  • Для абсолютного большинства поисковых запросов для правильной идентификации достаточно коротких фрагментов 5-10 секунд.
  • Качество поиска слабо зависит от разрешения видео (в определённых пределах).
  • Поиск идёт исключительно по видео. Аудио из процесса полностью исключено. Плюс в том, что несколько версий одного и того же фильма с разными звуковыми дорожками в результате поиска приведут к одному и тому же фильму. Это исключает ненужное дублирование и, как следствие, экономит ресурсы.

Минусы


  • Поиск необходимо вести от начала и до конца. Т.е. при поступлении запроса необходимо сравнить его со всеми образцами в базе данных. Это накладывает определённые ограничения не только на тип памяти для хранения информации, но и для размеров этой самой памяти. Для того чтобы получить ответ за несколько секунд необходимо, чтобы индексы находились в оперативной памяти. Чем больше база, тем больше места в ОЗУ выделено для хранения информации и тем дольше будет длиться поиск. Например, для 2-х канального доступа и при использовании памяти DDR3 частотой 1600 МГц для поиска по базе размером 12 ГБ понадобится минимум около 0,5 секунды. Для базы размером 48 ГБ необходимо будет уже порядка 2-х секунд минимум.
  • Для очень темных или очень светлых мест в видео (обычно это эффекты перехода между сценами) на коротких исходных фрагментах поиск будет работать плохо. Будут многочисленные совпадения. Что, в общем то, вполне понятно, но неприятно.
  • Также будут проблемы идентификации с начальными заставками компаний производителей видео или с сериалами. Что, в общем то тоже, вполне понятно. Это не проблема алгоритма это дубликаты данных.
  • Качество поиска может сильно зависеть от обрезки видео по краям.

Поиск по изображениям


Основная идея


Разбиваем исходное изображение на ячейки таблицы M x N. Находим усреднённое значение красной, зелёной и синей компоненты в каждой из областей. Собственно набор этих значений и будет характеристикой этого изображения, с помощью которой мы сможем их все отличать друг от друга. Заносим эту характеристику в базу данных вместе с указателем на описание видео (Video ID) и порядковым номером кадра в видео. Остаётся лишь вопрос, какие значения принимают M и N? Мы взяли 5 x 5, но Вы можете попробовать другие значения. При небольших значениях этих параметров есть шанс что у нас будет много дубликатов, а при больших мы потратим много памяти.



Однако это ещё не всё. Если в дальнейшем осуществлять поиск по всем этим характеристикам, то на обработку каждого запроса будет уходить много времени! Как же быть? Можно подсчитать усреднённое значение R, G, B компонент для этого изображения и на основе этих значений группировать их в массиве данных. Например: R=200, G=188, B=212. В этом случае мы заносим информацию о кадре в соответствующий раздел или добавляем поле в таблицу. А при поиске аналогично определяем эти компоненты и ищем с учётом этих параметров. Таким образом, мы во много раз сужаем объём сравниваемых данных и ускоряем поиск.



Если честно, то это только в теории, на практике всё немного иначе. Но это тема для отдельной статьи.

Плюсы


  • Относительно небольшой размер данных.
  • Есть возможность разбития всех данных на группы и осуществлять поиск по группам, что существенно ускоряет поиск.
  • Не требует постоянного хранения больших массивов данных в ОЗУ в отличии от предыдущего метода.
  • Низкая вероятность ошибки.


Минусы


  • Вследствие того, что после перекодирования видео может несколько отличаться от оригинала, да и JPEG-кодирование (при поиске по изображению) меняет оригинал и группа может быть определена неверно. Это требует либо расширения диапазона группы (приводит к уменьшению скорости поиска) либо к дополнительным поисковым запросам (тоже замедляет поиск).

Инструментарий


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

Поиск видео (клиентская часть)


  • Через веб-форму на сайте
  • Через приложение Video Color Capture

Поиск видео (серверная часть)


  • Video Color Server. Существует две версии: Windows (работает как сервис) и Linux (работает под обычным пользователем, запуск через crontab).

Добавление видео


  • Через приложение Video Color Creator



Области применения поиска видео по видео фрагментам


  • Идентификация старых и неизвестных видео фильмов.
  • Нахождение и отсечение рекламных блоков.
  • Проверка частей видео на предмет заимствования их из других фильмов (плагиат).
  • Определение точной даты публикации и названия шоу (передачи) если в репосте отсутствует данная информация.
  • Определение более-менее точной позиции проигрываемого потокового видео, если идёт вещание ранее проиндексированного видео.

Идентификация старых и неизвестных видео фильмов


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

Нахождение и отсечение рекламных блоков


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

Определение точной даты публикации и названия шоу (передачи) если в репосте отсутствует данная информация


Пример: Вы смотрите видео-шоу, размещённое на неизвестном сайте. Возможно, вы даже знаете, как это шоу называется, но не знаете, когда оно было показано. Год назад или два?

Определение более-менее точной позиции проигрываемого потокового видео, если идёт вещание ранее проиндексированного видео


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

Как пользоваться сервисом


Поиск видео через веб-форму на сайте


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



Следует заметить, что в случае загрузки видео фрагмента сервер будет сначала заниматься раскадровкой загруженного видео и его обработкой, что потребует дополнительного времени.

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



Поиск видео с помощью приложения


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









Можно ли в одиночку наполнить содержимое базы данных индексной информацией об одном миллионе видео?


Скорее всего нет. Где взять эти видео? Как их прокачать по сети? Где взять вычислительные ресурсы для их обработки?

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



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



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



Планы на будущее


  • Ускорение поиска.
  • Повышение точности поиска.
  • Поиск по аудио фрагментам.

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

Итоги


  • В данной публикации мы рассмотрели текущее положение дел с поиском видео.
  • Ознакомились с методами поиска видео по короткому видео фрагменту и изображению.
  • Рассказали о приложении для поиска видео Video Color Capture.
  • Упомянули о приложении Video Color Creator для добавления в общую базу данных видео компании AAP Software.

Ссылки


Сайт


http://www.videocolor.aapsoftware.ru/
На сайте доступен поиск по короткому видео фрагменту, а также по изображению из видео.

Приложения


  • Windows x64 приложение для идентификации видео Video Color Capture
  • Windows x64 приложение для добавления видео в базу данных Video Color Creator
  • Все приложения бесплатны.

Видео



Публикации


Подробнее..

Монополии цифровых гигантов кто защитит потребителей?

22.10.2020 18:09:59 | Автор: admin
На этой неделе Минюст США подал иск против Google, обвинив компанию в монополии на поиск и поисковую рекламу. В иске утверждается, что Google злоупотребляет своим положением на рынке интернет-поиска и рекламы и подавляет конкурентов. Отличное продолжение темы, которую мы в прошлый раз обсуждали об использовании интернет-поиска Яндексом для продвижения собственных сервисов.

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



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

В России ситуация с рынком интернет-поиска складывается похожим образом, как и за рубежом. У нас свой поисковик номер один Яндекс. Его доля оценивается в 60%, еще порядка 35% у Google. И Яндекс не первый год продвигает собственные сервисы, манипулируя результатами поисковой выдачи и нанося вред конкуренции в цифровой среде, не хуже своего американского аналога.

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

Стоит понимать, что современный Яндекс уже не просто поисковая система, этот игрок другой лиги. Он создал рынок, и на этом рынке должны действовать общие для всех правила. В конце концов, не того ли российский IT-гигант требовал от Google прямо здесь, на Хабре? И почему бы не применить эти требования к самому себе?

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

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

Техногиганты контролируют и блокируют ключевые части интернет-инфраструктуры, тем самым перекрывая путь на рынок начинающим компаниям, а также распоряжаются по своему усмотрению трафиком в поисковых системах. Какова вероятность для Моськи побороть слона?

В ряде случаев IT-гиганты наказывают за такое поведение. Так, например, в 2017 г. Еврокомиссия оштрафовала Google за приоритетное продвижение собственного сервиса Google Shopping в своей поисковой системе. Обратите внимание, в деле Google речь шла об эксклюзивном продвижении всего одного сервиса, в то время как Яндекс использует колдунщики для продвижения множества своих сервисов в формате, недоступном остальным участникам рынка.

Например, по запросу смотреть боевик онлайн поисковая система Яндекса всегда приоритетно показывает свой сервис КиноПоиск, смещая вниз другие строки выдачи. Аналогично работают колдунщики и для других рынков. Так, выдавая поисковые результаты по запросу купить авто, колдунщики продвигают аффилированный c Яндексом сервис auto.ru.В поиске услуг Яндекс.Услуги, в сегменте географических запросов продвигаются Яндекс.Карты.



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



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

В России уже есть знаковый прецедент: восемь ИТ-компаний, которые назвали себя ИТ-Коалиция обратились в ФАС с просьбой остановить злоупотребление доминирующим положением компании Яндекс на рекламном рынке, которое, по сути, устраняет конкуренцию между сервисами Яндекса и независимыми компаниями. В конце концов, регуляторы для этого и существуют, чтобы не допустить монопольного положения на рынке и злоупотребления этим положением.

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

Перевод В 2020 году две трети поисковых запросов в Google завершалось без нажатия на ссылку

24.03.2021 12:04:07 | Автор: admin


В августе 2019 года было опубликовано исследование ныне уже несуществующего поставщика данных о посещениях Jumpshot, демонстрирующее, что 50,33% всех поисковых запросов Google завершалось без клика на веб-ресурсы в результатах поиска. Сегодня благодаря новым данным SimilarWeb удалось внести в этот анализ существенное дополнение.

С января по декабрь 2020 года 64,82% поисковых запросов в Google (суммарные данные по десктопам и мобильным устройствам) завершилось результатами поиска без кликов на сторонние веб-ресурсы. Вероятно, в этой статистике недооценены некоторые мобильные и почти все голосовые запросы, поэтому возможно, что более двух третей всех поисковых запросов Google являются тем, что называется запросами с нулевыми кликами (zero-click searches). Некоторые специалисты указывают, что нулевые клики это немного ошибочный термин, поскольку к этой группе относится и поиск, который завершается кликом внутри самого Google SERP (например, при нажатии на звуки животных здесь или при нажатии на номер телефона на поле с картой). Однако терминология, похоже, устоялась, поэтому её нужно объяснить.

Предупреждение: надо сказать, что эти данные нельзя напрямую сравнивать с теми, которые были опубликованы в 2019 году, потому что выборка данных посещений SimilarWeb отличается от данных Jumpshot. Во-первых, данные за 2020 год учитывают весь мир, а в предыдущем анализе Jumpshot приведена выборка только по США. Кроме того, SimilarWeb объединяет и мобильные, и десктопные устройства, в том числе и устройства Apple/iOS (к которым у Jumpshot не было доступа). Тем не менее, высока вероятность того, что если бы предыдущая выборка по-прежнему была доступна, она бы демонстрировала похожий тренд на увеличение поедания кликов сервисом Google.

Вот основная статистика по данным:

  • SimilarWeb проанализировал примерно 5,1 триллиона поисковых запросов Google за 2020 год
  • Эти запросы выполнялись на более чем 100 миллионах мобильных и десктопных устройств, с которых SimilarWeb собирает данные о посещениях
  • Из этих 5,1 триллионов поисковых запросов 33,59% привело к кликам на органические результаты поиска
  • 1,59% привело к кликам на платные результаты поиска
  • Оставшиеся 64,82% завершили поиск без непосредственного клика на сторонний веб-ресурс
  • Процент поисковых запросов, которые привели к клику, гораздо выше на десктопных устройствах (органический CTR 50,75%, платный CTR 2,78%)
  • На мобильных устройствах поисковых запросов с нулевыми кликами гораздо больше (77,22%)

Я создал несколько графиков этих данных, однако полный охват собранной SimilarWeb информации гораздо богаче.

Интересно рассмотреть CTR мобильных и десктопных устройств по отдельности.


Как видно из диаграммы, органический CTR на десктопах по-прежнему больше 50%. Десктопные устройства (которые в этом анализе включают в себя ноутбуки и большие планшеты) также демонстрируют чуть больший CTR платных ссылок. Подозреваю, что в США эти показатели побольше, а в странах и регионах с меньшим количеством рекламодателей и меньшими бюджетами на рекламу, они, скорее всего, ниже.


Этот график показывает, насколько сильно ситуация отличается для мобильных устройств, на которых производится гораздо больше поисковых запросов по всему миру, в США и в выборке SimilarWeb. Здесь мы видим, что усилия Google по оптимизации поиска в первую очередь для мобильных устройств принесли огромные дивиденды для трёх четвертей всех мобильных поисковых запросов клики необязательны.

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

Однако я считаю важным предоставить некоторый контекст относительно мощи монополии Google. В 2020 году поисковый гигант имел более 91% всемирной доли рынка и более 87% рынка США. Как говорится в исследовании GroupM, опубликованном на прошлой неделе в Wall Street Journal, Google также контролирует огромную долю всей рекламы в США, в том числе более 95% поисковой рекламы и более 50% дисплейной рекламы.


Источник: The Wall Street Journal

Однако Google / Alphabet планируют на этом остановиться. Шосана Водински из Gizmodo в своём анализе планов Google по замене рекламных куки проприетарной системой Google для сбора информации о поведении пользователей под названием FLoC сообщила следующее:

Этот план предоставит Google эксклюзивный доступ к огромному объёму пользовательских данных, которые компания и так по большей мере монополизировала.

Такое доминирование это пугающая, но кажущаяся неизбежной перспектива. Аналогично тому, как GDPR препятствовал любым шансам европейских технологических компаний на конкуренцию с технологическими гигантами США, этот шаг по реализации приватности, похоже, в прогнозируемом будущем гарантирует господство Google в сфере поисковой онлайн-рекламы и в вебе (той его части, которая не разделена между Facebook и Amazon).

Для наглядности я изучил данные с января 2018 года (самые ранние данные SimilarWeb, вошедшие в этот проект) по декабрь 2020 года.


График немного скачет, но мы можем сделать несколько любопытных выводов:

  • Общий объём поисковых запросов растёт, однако в падении в конце 2019 года может быть виновна пандемия (может быть, причина и в уменьшении выборки SW, сложно об этом судить)
  • Доля платных поисковых запросов очевидно растёт, и если внимательнее изучить числа, это справедливо и для мобильных, и для десктопных устройств
  • Органические клики выросли в 2020 после долгого плато и небольшого снижения. Похоже, это вызвано увеличением доли использования десктопов в 2020 году (опять из-за Covid-19, вынудившего нас больше сидеть за большими экранами), поэтому после вакцинации ситуация, возможно, поменяется.
  • В конце 2020 года зафиксирована самая высокая доля поисковых запросов с нулевыми кликами. Вероятно, эксперимент Google с ограничением featured snippets (быстрых ответов) в первом квартале (который сейчас, похоже, закончился) может продемонстрировать любопытные корреляции.

Хорошие новости: сегодня в Google как никогда много поисковых запросов и доступно больше кликов.

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



На правах рекламы


Эпичные серверы это надёжные серверы на Linux или Windows с мощными процессорами семейства AMD EPYC и очень быстрой файловой системой, используем исключительно NVMe диски от Intel. Попробуйте как можно быстрее!

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru