LINUX.ORG.RU

Запил поискового движка на C++

 


1

4

Да, можно взять elasticsearch, postgres, что-то ещё. Но мы тут не про «взять», а про «запилить», development же.

Так вот, я ищу слово «дур». Хочу, чтобы находилось как в телеграме:

дурь
дурью
дури
дуру
дура

Возможно, «дурка», но вряд-ли - корень другой.

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

Вот давайте по-простому на уровне объяснения алгоритма школьнику.

Что можно сделать ДЛЯ НАЧАЛА? При индексации, когда мы смотрим на документ 12345 и встречаем слово «придурью» то мы берём корень и само слово. Далее пишем в индекс 2 такие записи и казалось бы достаточно.

придурью -> 12345,position
дур -> 12345,position

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



Последнее исправление: lesopilorama (всего исправлений: 7)
Ответ на: комментарий от static_lab

Да это-то понятно. Ну и видимо это тупо основа. Ну то есть, утверждение «у меня есть стеммер» автоматически уже означает, что у тебя будет норм поиск.

lesopilorama
() автор топика
Последнее исправление: lesopilorama (всего исправлений: 1)

Поиску (теоретическому) всё равно что поглощать, он больше про расстояние Левенштейна, чем про реальные слова.

Допустим у Вас есть текст Т. И по нему производится поиск фразой Ф. В тексте присутствует похожая фраза. И редакционное расстояние для неё Р. Если Р слишком большое, то отбрасывается вообще. Если подходит, то на основе Р сортируется результат. Вот и всё.

Ну там ещё по абзацам можно разбить.

И так имея Н текстов, вы их можете отсортировать их по совпадению для фразы Ф.


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

Есть текст.

Без петель и замков дом. Слиток золота спрятан в нем.

Для поиска по слову «петля», например, для этого текста будет редакционное расстояние - ПР.

А для слова «головокружительно» – ГР. И оно очень большое. Следовательно слово с этим текстом не связывается никак.

Есть фраза: «Пирожок спрятан в доме».

По каждому слову находите расстояние.

Потом по всем текстам, по некой границе фильтруете. Остаток сортируете по наименьшему расстоянию.

Выдача поисковика.

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

thegoldone ★★
()

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

Да вроде Сфинкс так себе. Отставший от жизни очень сильно. Я не слежу за ним. Просто когда в последний раз трогал – это никуда не годилось. Время-то на месте не стоит.

thegoldone ★★
()

Пусть у тебя есть набор пронумерованных документов. Разбиваешь документы на слова, строишь индекс вида слово1→docid1,docid7,docid999;слово2->docid1,docid99,docid1000. Номера документов стоит отсортировать для оптимизации, а также с ними имеет смысл хранить позицию(и) слова в документе.

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

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

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

Ещё одно направление - расширение запроса, то есть на данный запрос сгенерировать его варианты с учётом семантики и статистики для улучшения полноты. Тут можно даже нейронки прикрутить.

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

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

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

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

anonymous
()

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

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

anonymous
()
Ответ на: комментарий от anonymous

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

rtxtxtrx ★★★
()

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

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)

Что можно сделать ДЛЯ НАЧАЛА?

Насколько мне известно, то классика это n-граммы. Разбиваешь слово по три буквы (например) и к этому уже применяешь запрос.

полудурок:
пол - 0
уду - 3
рок - 6

запрос «дур» сопоставляется с n-граммами «уду» + «рок».

urxvt ★★★★★
()
Ответ на: комментарий от thegoldone

Поиску (теоретическому) всё равно что поглощать, он больше про расстояние Левенштейна, чем про реальные слова.

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

maxcom ★★★★★
()

Что можно сделать ДЛЯ НАЧАЛА?

Судя по примеру, тебе может подойти суффиксное дерево или суффиксный массив + инвертированный индекс.

Суффиксные деревья/массивы не разбирают слово на составляющие, тупо хранят как набор символов, т.е. «дур» будет матчить «дурка» и «придурью».

Есть куча статей по этому вопросу, можно начинать с википедии.

Кратенько по делу от опытных, кто делал.

Я делал trie базу данных (https://github.com/vmxdev/tkvdb/), знаю что ее использовали для индексации и быстрого поиска текстов в «Книге Мормона».

Но они использовали английский и ASCII-кодировку, это максимально простой вариант - 1 байт на символ. С юникодом нужно будет немного попердолиться. Это, конечно, опционально, можно сильно не заморачиваться.

vmx1
()
Ответ на: комментарий от thegoldone

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

firkax ★★★★★
()
Ответ на: комментарий от anonymous

Запрос «дур» у тебя ни с чем не сопоставляется. Триграммы это не «пол, уду, рок», а «пол, олу, луд, уду, дур, уро, рок». Вот тут сопоставится.

Он учитывает позиции ngramm. Условно говоря, ищет где идут две ngramm'ы подряд, из которых первая заканчивается на «д» а вторая начинается на «ур».
Если я все верно помню, то когда-то давно Russ Cox в блоге описывал как он делал в Гугле их сервис Code Search (публичный который был. Нынче похоронен). Так он там именно так и работал.

urxvt ★★★★★
()
Ответ на: комментарий от maxcom

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

Расширением запроса можно искать за O(длины запроса), а точнее в длина запроса * размер алфавита раз медленнее чем точный поиск.

anonymous
()
Ответ на: комментарий от urxvt

Кто «он»? Опиши свой алгоритм. Если у тебя нет триграммы «дур» в индексе, тебе придётся перебрать все триграммы, составить из них пары тех которые при конкатенации дают «дур», вытащить из индекса их кишки и мучительно их сливать перебирая в разы больше кандидатов чем могло бы быть в одной кишке для триграммы «дур».

anonymous
()
Ответ на: комментарий от peregrine

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

anonymous
()
Ответ на: комментарий от vmx1

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

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

anonymous
()
Ответ на: комментарий от anonymous

Кто «он»?

Алгоритм.

Согласен, в целом ты прав.

Так то я имел ввиду то, что если индекс позволяет тебе быстро получить данные по запросу вида «найти n-граммы по префиксу/суффиксу», то можно построить запрос вида `дур OR *д OR ур* OR *ду OR *р`. Получив в ответ список из n-грамм и их позиций можно понять, складываются ли они в слово `дур` или нет.
Как сделать индекс n-грамм с возможностью поиска по префиксу и суффиксу одновременно? Например с помощью двух префиксных деревьев, одно для префикса и другое для суффикса.

urxvt ★★★★★
()
Ответ на: комментарий от maxcom

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

Это делается на этапе индексирования. Во время поиска всё уже найдено и отранжировано.

У Левенштейна редакционное расстояние. Эту функцию для поиска следует адаптировать. Например «вилка» и «нож» – абсолютно разные слова. Как и «и» и «я». Но редакционное расстояние у второй пары мизерное. Если учесть всё это, можно создать свою функцию нахождения поисковой дистанции.

Бонусом, удаление из выдачи элементарно.

thegoldone ★★
()
Ответ на: комментарий от firkax

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

Да, только всё наоборот.

thegoldone ★★
()
Ответ на: комментарий от thegoldone

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

firkax ★★★★★
()

Тред не читал, стартовый пост тоже. Классика жанра в вопросе запила поискового движка – Managing Gigabytes под авторством Witten, Moffat, Bell. Из более свежих хорош An Introduction to Information Retrieval, C. D. Manning’а.

Pwner
()
Ответ на: комментарий от thegoldone

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

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

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

anonymous
()
Ответ на: комментарий от anonymous

Если поисковый движок работает по разному. В зависимости от языка. То это туфта на палочке. Стемминг, фигеминг, приставки. Отличные маркеры фигового поискового движка.

А я могу придумать язык. Написать на нём текст. И найти. Вот и всё. Тут нечего обсуждать. Уже давно всё расставило время по своим местам. Нет смысла сейчас повторять историю.

Что Сфинкс. Что Яндекс. Что другие (Рамблер). В своё время упарывались по языку. Кто-то из них понял, что путь никакущий. А кто-то до сих пор не понимает.

Помимо расстояния Левенштейна есть ещё много чего. Когда два текста можно сравнить по близости тематик, например. И этот алгоритм точно так же не учитывает никакие языки. Но да мне лень расписывать давние прописные истины по полочкам.

Откройте для себя стеммер snowball и воображаете эксперта в поиске.

thegoldone ★★
()
Ответ на: комментарий от anonymous

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

thegoldone ★★
()
Последнее исправление: thegoldone (всего исправлений: 1)
Ответ на: комментарий от thegoldone

А я могу придумать язык. Написать на нём текст. И найти. Вот и всё.

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

Тут нечего обсуждать. Уже давно всё расставило время по своим местам. Нет смысла сейчас повторять историю.

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

Но да мне лень расписывать давние прописные истины по полочкам

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

anonymous
()
Ответ на: комментарий от anonymous

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

Это когда Вы Ctrl+F нажимаете в браузере? Там просто точное соответствие.

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

thegoldone ★★
()

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

foror ★★★★★
()
Ответ на: комментарий от foror

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

foror ★★★★★
()
Ответ на: комментарий от lesopilorama

Очепятки идут накуй, очевидно.

Опечатки исправляются, по желанию, до поиска.

Суть поискового движка в выдаче результатов по запросу. Ядро его работает с чистым запросом. Который может быть подготовлен. Как и выдача ядра может иметь постобработку.

Нет смысла всё это рассматривать как единую задачу.

Исправление ввода и предложения Вы можете добавить или не добавлять. А можете добавить и заменить потом. Никак не затрагивая ядро.

Левеншнейн - медленно в реализации.

Не предлагаю использовать Левенштейна в запросе. А при индексации.

В принципе не Левенштейна, а свою функцию.

Если есть слово «член» и от него к слову «члены» ведёт расстояние N, которое может рассматриваться как аналог. То просто связываете их и всё. И при запросе «члены», иметь выдачу и для слова «член», и для слова «члены».

Для слова «многочлен» расстояние > N, допустим. И оно не связано.

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


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

thegoldone ★★
()

Поиск может быть двухстадийным. Сначала по слову «дур» ищутся индексы, в которых оно содержится. Затем по каждому индексу ищутся проиндексированные документы. Множества найденных документов объединяются.

И не надо извращаться при индексации.

anonymous_incognito ★★★★★
()
Ответ на: комментарий от anonymous

Но это задача на звёздочку, для русского хватит перейти на u32.

Для русского (как и для всех остальных живых языков) за глаза хватит u16

Хотя вообще да, там есть ещё combining characters, которые тоже по-хорошему нужно считать одним символом с родителем, либо нормализовывать.

ICU в помощь.

annulen ★★★★★
()
Последнее исправление: annulen (всего исправлений: 1)
Ответ на: комментарий от peregrine

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

lesopilorama
() автор топика
Ответ на: комментарий от lesopilorama

Тем более вопрос не сложный, пара вечеров на прототип

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

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)

это фонетический поиск, для русского есть алгоритм Кондрашова если хочешь простого запили свой аналог soundex :)

гласные выкидываешь, оставляешь первую согласную остальные 3 кодируешь дура дурь дуру и т.д. все будет Д7 скажем.

то что ты так просто пишешь берем корень, ну не так быстро и просто как его взять то …

cylon17
()

Для начала сделай n-gram поиск, тупо проиндексируй все куски слов длиной >=n. Для всего остального понадобится подробный словарь, иначе будет сокращать всякие подвиги и надиры до несуществующих корней.

neumond
()