Експерименти з GPT-3 у розробці пошукових рішень

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Вітаю! Мене звати Олександр Книга, я Solution Architect у Weblab Technology. Захоплююсь розробкою та open source, тому в цій статті пропоную розібрати мій експериментальний кейс використання OpenAI embedding API на основі GPT-3 в реалізації пошукових рішень. Це допоможе оцінити переваги та недоліки цієї моделі, а також наочно протестити її на різних пошукових запитах.

Стаття корисна тим, хто зацікавлений в покращенні якості пошуку з можливістю розуміння контексту запитів в рішеннях з великою кількістю контенту.

Кастомні варіанти рішень для пошуку

Навіть якщо потреба в запитах і загалом пошуку є дуже поширеною, реалізації пошукового функціоналу можуть дуже сильно відрізнятись. У більшості випадків компанії використовують найпростіший метод, просто надсилаючи запити безпосередньо до OLTP баз даних. Запити можуть виглядати так: "SELECT id, title FROM, entities WHERE, description LIKE '%bow%".

Однак частіше вони представлені у вигляді складних, багаторівневих повільних структур, з’єднання таблиць, які неможливо прочитати. Подібні запити не здатні розуміти контекст, вимагають численних налаштувань та їх складно реалізувати правильно. Хоча можна покращити час виконання запитів за допомогою materialized view, кешування запитів та інших технік, додаткове ускладнення призводить до значної затримки між первинними оновленнями та консистентними результатами пошуку.

Ефективнішою альтернативою примітивним пошуковим рішенням на основі баз даних можуть бути пошукові системи з відкритим вихідним кодом, такі як Apache Lucene, Apache Solr, Elasticsearch, Sphinx, MeiliSearch, Typesense тощо. Вони, як правило, порівняно швидші, набагато краще справляються зі складними запитами та працюють з фільтрами. Але якщо порівняти ці пошукові системи з аналогами на кшталт Google Search або DuckDuckGo, стає зрозуміло, що open-source рішення не здатні створити належний пошуковий контекст і модальності запиту — вони не здатні зрозуміти запит, якщо користувач надає нечіткі умови.

Значення нечіткого пошукового запиту

Уявіть, що ви ніяк не можете згадати назву жовтого цитруса з кислуватим смаком. Але хочете знайти в застосунку статтю, як виростити цей загадковий фрукт. Яким чином буде здійснюватися пошук? Ваш запит може бути таким: «Як виростити жовтий кислий цитрус в кімнатних умовах». Будь-яка з вищезгаданих пошукових систем з відкритим вихідним кодом може мати значні труднощі з видачею релевантних результатів за цим запитом, навіть якщо в базі даних є статті про вирощування «лимонів».

Це відбувається тому, що вилучення значення із запиту є завданням природної мови (NLP) і навряд чи може бути вирішене без компонентів машинного навчання. GPT-3 добре справляється з цим завданням. OpenAI пропонує embedding API на основі GPT-3, який перетворює текст (natural language text) у вектор чисел з плавучою комою.

Embedding — це, по суті, простір низької розмірності, в який можуть бути переведені вектори високої розмірності. У цьому випадку вектор високої розмірності — це текст, а низької — вихідний вектор фіксованого розміру. Відстань між векторами показує, наскільки сильно вони корелюють. Чим менша відстань, тим вища й кореляція. Якщо перевизначити текст як векторну форму, то задача зводиться до класичної проблеми ML-пошуку.

Вибір моделі

Перетворення тексту документа у векторне представлення може відбуватися у фоновому режимі, тоді як векторизація пошукового запиту має відбуватися під час його виконання. Існує кілька сімейств GPT-3 моделей, які пропонує OpenAI:

text-search-ada-doc-001: 1024
text-search-babbage-doc-001: 2048
text-search-curie-doc-001: 4096
text-search-davinci-doc-001: 12288

Більша розмірність вектора призводить до більшої кількості вбудованої інформації, а отже, до більших витрат і повільнішого пошуку.

Документи зазвичай великі за обсягом, а запити, як правило, короткі та неповні. Тому векторизація будь-якого документа значно відрізняється від векторизації запиту, враховуючи щільність і розмір контенту. OpenAI знає це, і тому пропонує дві парні моделі, ‘-doc’ and ‘-query’:

text-search-ada-query-001: 1024
text-search-babbage-query-001: 2048
text-search-curie-queryc-001: 4096
text-search-davinci-query-001: 12288

Важливо зазначити, що запит і документ повинні використовувати одне і те ж сімейство моделей і мати однакову довжину вихідного вектора.

Експериментальний датасет

Зрозуміти потужність цього пошукового рішення найкраще можна на прикладі. Для цього візьмемо набір даних TMDB 5000 Movie Dataset, який містить метадані про приблизно 5 000 фільмів з TMDb. Ми побудуємо пошукове рішення, спираючись лише на описи фільмів, не беручи до уваги їхні рецензії.

Наш процес векторизації буде побудований лише на основі заголовка та опису.

Embeddings diagram image

Приклад запису:

Title: Harry Potter and the Half-Blood Prince
Overview: As Harry begins his sixth year at Hogwarts, he discovers an old book marked as ’Property of the Half-Blood Prince’, and begins to learn more about Lord Voldemort’s dark past.

Тепер перетворимо датасет на готовий до індексації текст:

datafile_path = "./tmdb_5000_movies.csv"
df = pd.read_csv(datafile_path)

def combined_info(row):
  columns = ['title', 'overview']
  columns_to_join = [f"{column.capitalize()}: {row[column]}" for column in columns]
  return '\n'.join(columns_to_join)
  
df['combined_info'] = df.apply(lambda row: combined_info(row), axis=1)

Embedding process доволі простий:

def get_embedding(text, model="text-search-babbage-doc-001"):
  text = text.replace("\n", " ")
  return openai.Embedding.create(input = [text], model=model)['data'][0]['embedding']

get_embedding(df['combined_info'][0])

Цей блок коду виводить список, розмір якого дорівнює параметрам, якими оперує модель, що у випадку 'text-search-babbage-doc-001' дорівнює 2048.

Аналогічний процес вбудовування слід застосувати до всіх документів, в яких ми хочемо здійснити пошук:

df['combined_info_search'] = df['combined_info'].apply(lambda x: get_embedding(x, model='text-search-babbage-doc-001'))
df.to_csv('./tmdb_5000_movies_search.csv', index=False)

Стовпець 'combined_info_search' міститиме векторне представлення комбінованого тексту.

І, як не дивно, на цьому все! Нарешті, можна виконати пошуковий запит:

from openai.embeddings_utils import get_embedding, cosine_similarity

def search_movies(df, query, n=3, pprint=True):
    embedding = get_embedding(
        query,
        engine="text-search-babbage-query-001"
    )
    df["similarities"] = df.combined_info_search.apply(lambda x: cosine_similarity(x, embedding))

    res = (
        df.sort_values("similarities", ascending=False)
        .head(n)
        .combined_info
    )
    if pprint:
        for r in res:
            print(r[:200])
            print()
    return res

res = search_movies(df, "movie about the wizardry school", n=3)

Отримані результати

Title: Harry Potter and the Philosopher’s Stone
Overview: Harry Potter has lived under the stairs at his aunt and uncle’s house his whole life. But on his 11th birthday, he learns he’s a powerful wizard — with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school’s kindly headmaster, Harry uncovers the truth about his parents’ deaths — and about the villain who’s to blame.

Title: Harry Potter and the Goblet of Fire
Overview: Harry starts his fourth year at Hogwarts, competes in the treacherous Triwizard Tournament and faces the evil Lord Voldemort. Ron and Hermione help Harry manage the pressure — but Voldemort lurks, awaiting his chance to destroy Harry and all that he stands for.

Title: Harry Potter and the Prisoner of Azkaban
Overview: Harry, Ron and Hermione return to Hogwarts for another magic-filled year. Harry comes face to face with danger yet again, this time in the form of an escaped convict, Sirius Black — and turns to sympathetic Professor Lupin for help.

Опис до Harry Potter and the Philosopher’s Stone містить слова «wizardry» та «school» і займає перше місце в пошуковій видачі. Другий результат вже не містить слова «school», але все ще містить слова, близькі до «wizardry», «Triwizard». Третій результат містить лише синонім слова «wizardry» — «magic».

Звичайно, в цій базі даних є безліч інших фільмів, в яких фігурують школи або чарівники (або і те, й інше), але це були єдині фільми, які було нам видано. Це наочний доказ того, що пошукове рішення працює і дійсно розуміє контекст нашого запиту.

Ми використовували модель Babbage, яка має лише 2048 параметрів. Davinci має в шість разів більше параметрів (12 288) і, таким чином, може значно краще працювати з дуже складними запитами.

Пошукове рішення може іноді не видавати результати, що відповідають деяким запитам. Наприклад, за запитом «movies about wizards in school»:

Title: Harry Potter and the Philosopher’s Stone
Overview: Harry Potter has lived under the stairs at his aunt and uncle’s house his whole life. But on his 11th birthday, he learns he’s a powerful wizard — with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school’s kindly headmaster, Harry uncovers the truth about his parents’ deaths — and about the villain who’s to blame.

Title: Dumb and Dumberer: When Harry Met Lloyd
Overview: This wacky prequel to the 1994 blockbuster goes back to the lame-brained Harry and Lloyd’s days as classmates at a Rhode Island high school, where the unprincipled principal puts the pair in remedial courses as part of a scheme to fleece the school.

Title: Harry Potter and the Prisoner of Azkaban
Overview: Harry, Ron and Hermione return to Hogwarts for another magic-filled year. Harry comes face to face with danger yet again, this time in the form of an escaped convict, Sirius Black — and turns to sympathetic Professor Lupin for help.

Що Dumb and Dumberer: When Harry Met Lloyd робить тут, запитаєте ви? Це дефекти моделей з відносно малою кількістю параметрів. Цей результат видачі не було відтворено із застосуванням моделі з більшою кількістю параметрів.

Обчислення відстані між запитом і документами

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

Image of the relevance output

У вищезгаданому прикладі ми використовували cosine similarity для обчислення відстані через високу розмірність векторного простору. З моделлю Babbage ми маємо 2048 параметрів.

Алгоритми обчислення відстані, як правило, представляють цю схожість (відмінність) між запитом і документом одним числом. Однак ми не можемо покладатися на евклідову відстань через «прокляття розмірності», адже відстані будуть занадто схожими. Це пов’язано з тим, що евклідова відстань стає вкрай непрактичною за межами приблизно семи вимірів — в цій точці відстані потрапляють в одні і ті ж кошики і стають майже ідентичними.

Якщо бажаєте, можете переглянути сформований репозиторій тут. Крім того, ви можете погратися з ним у Google Colab за цим посиланням.

Часова складність (Time complexity)

Для сортування документів ми використали метод прямого перебору. Визначимо:

  • n: кількість точок у навчальному наборі даних;
  • d: розмірність даних.

Часова складність пошуку при переборі становить O(n * d * n * log(n)). Параметр d залежить від моделі (у випадку Babbage він дорівнює 2048), а блок O(nlog(n)) обумовлений етапом сортування.

На цьому кроці нам важливо пам’ятати, що менші моделі працюють швидше і дешевше. Наприклад, на етапі обчислення схожості пошукового запиту модель Ada працює вдвічі швидше, тоді як модель Davinci — вшестеро повільніше відносно Babbage.

Обчислення cosine similarity між 2048-вимірним запитом і 4803 документами зайняло 1260 мс на CPU M1 Pro. У поточній реалізації час, необхідний для обчислень, зростатиме лінійно до загальної кількості документів. Водночас цей підхід підтримує паралелізацію обчислень.

Альтернативи прямому перебору

У пошукових рішеннях реального часу запити мають бути виконані якомога швидше. Зазвичай за це доводиться платити часом навчання та попереднього кешування. Ми можемо використовувати такі структури даних, як k-d-tree, r-tree або ball tree. Розглянемо статтю з Towards Data Science про аналіз обчислювальної складності цих методів: всі вони призводять до обчислювальної складності, близької до O(k * log(n)), де k — це кількість елементів, які ми хотіли б отримати в межах одного батчу.

K-d-дерева, ball дерева та r-дерева — це структури даних, які використовуються для зберігання та ефективного пошуку точок у N-вимірному просторі, таких як наші вектори значень.

K-d та ball дерева — це деревоподібні структури даних, які використовують ітеративну бінарну схему розбиття простору на області, де кожна вершина дерева представляє підобласть. K-d-дерева особливо ефективні для пошуку точок у певному діапазоні або для пошуку найближчого сусіда до заданої точки.

Аналогічно, r-дерева також використовуються для зберігання точок у N-вимірному просторі, проте вони набагато ефективніші для пошуку точок у визначеній області або для пошуку всіх точок, що знаходяться на певній відстані від заданої точки. Важливо, що r-дерева використовують іншу схему розбиття, ніж k-d-дерева та ball дерева, адже вони ділять простір на «прямокутники», а не на бінарні розділи.

Імплементації дерев виходять за рамки цієї статті, і різні імплементації призведуть до отримання різних результатів пошуку.

Час запиту

Ймовірно, найсуттєвішим недоліком поточного рішення для пошуку є те, що нам необхідно викликати зовнішнє OpenAI API, щоб отримати embedding вектор запиту. Незалежно від того, наскільки швидко наш алгоритм зможе знайти найближчих сусідів, буде потрібен виклик зовнішнього не дуже швидкого та надійного API.

Text-search-babbage-query-001
Number of dimensions: 2048
Number of queries: 100
Average duration: 225ms
Median duration: 207ms
Max duration: 1301ms
Min duration: 176ms



Text-search-ada-query-002
Number of dimensions: 1536
Number of queries: 100
Average duration: 264ms
Median duration: 250ms
Max duration: 859ms
Min duration: 215ms



Text-search-davinci-query-001
Number of dimensions: 12288
Number of queries: 100
Average duration: 379ms
Median duration: 364ms
Max duration: 1161ms
Min duration: 271ms

Якщо ми візьмемо медіану за точку відліку, то побачимо, що модель ada-002 на +28% повільніша від babbage-001, а davinci-001 повільніша на +76%.

Обмеження GPT-3 Search Embeddings

Відповідно до статті Нільса Реймера (Nils Reimer) про порівняння embeddings моделей, можна зробити висновок, що GPT-3 не забезпечує виняткової ефективності або якості виводу і вимагає залежності від зовнішнього API, який є досить повільним.

GPT-3 має можливість підтримувати вхідні дані довжиною до 4096 токенів (приблизно 3072 слова), однак через API недоступний сервіс усікання, і спроба кодування тексту, довшого за 4096 токенів, призведе до помилки. Таким чином, користувач (тобто ви) несете відповідальність за визначення обсягу тексту, який може бути закодований.

Крім того, вартість навчання з OpenAI є відносно високою.

Як альтернативу, ви можете спробувати TAS-B або multi-qa-mpnet-base-dot-v1.

Approximate k-NN в Elasticsearch

Elasticsearch 8.0 підтримує ефективний евристичний пошук найближчих сусідів (ANN), який може бути використаний для розв’язання нашої задачі швидше, ніж будь-який лінійний KNN. Elasticsearch 8.0 використовує алгоритм ANN під назвою Hierarchical Navigable Small World graphs (HNSW) для організації векторів у графи на основі подібності.

Під час тестування на наборі даних з 10 мільйонів вбудованих векторів ми досягли вражаючої продуктивності — 200 запитів на секунду з ANN на одній обчислювальній машині, в той час, як при використанні KNN було виконано лише 2 запити на секунду. Обидва методи були надані Elasticsearch.

Відповідно до публікації в блозі ElasticSearch про випуск ANN і бенчмарку для щільного векторного пошуку, ціна такої продуктивності становить 5% від кількості запитів. Це означає, що приблизно 1 з 10 знайдених найближчих векторів не є справді відносним. Ми застосували гібридний підхід, щоб покращити цей результат: замість k запитували k+l і застосували фільтрацію для видалення пропусків. На жаль, немає можливості пагінації. Отже, цей підхід буде працювати лише в деяких випадках.

Висновок

Сподіваюсь, ви переконалися, що GPT-3 Embeddings не є ідеальним рішенням для всіх проблем пошуку через складність індексування, вартість, а також високу обчислювальну складність операції пошуку, навіть наближеного. З усім тим, GPT-3 Embeddings залишається чудовим вибором для тих, хто прагне отримати потужну основу для пошукового рішення з нечастими запитами та помірними вимогами до індексації.

Варто також додати, що нещодавно в Microsoft оголосили, що пошукова система Bing тепер використовує нову оновлену версію GPT 3.5, яка називається Prometheus і була розроблена від початку саме для пошуку. Згідно з повідомленням, нова мовна модель Prometheus дозволяє Bing підвищити релевантність, точніше анотувати фрагменти, надавати свіжіші результати, розуміти геолокацію та покращити безпеку. Це може відкрити нові можливості для використання авторегресійної мовної моделі для пошукових рішень, за якими ми обов’язково будемо стежити надалі.

Джерела:

  1. New and Improved Embedding Model
  2. k nearest neighbors computational complexity
  3. scipy.spatial.KDTree
  4. k-d tree
  5. OpenAI GPT-3 Text Embeddings — Really a new state-of-the-art in dense text embeddings?
  6. Introducing approximate nearest neighbor search in Elasticsearch 8.0
👍ПодобаєтьсяСподобалось83
До обраногоВ обраному15
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

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

як щодо Copilot X і можливості кодити голосом? Майбутнє вже зовсім поруч

Тему розгойдали на Reddit та Vice — це поки що більше хайп, але я вірю, що наша продуктивність зросте за рахунок зменшення часових витрат

«3 години за 0,11 долара» замість 2 тижнів розробника можуть трохи зменшити ваш скепсис

а после этих 3 часов за 0.11$ нужно будет что-то заменить и тем же способом этого добиться не получится и вот тут разработчик скажет что надо не 2 недели, а 2 месяца чтобы с этим разобраться и вообще лучше все переписать и т.д. это же классический пример «скупой платит дважды»

Я теж бачила ці статті, але поки скептично ставлюся до того, що відбувається)

вже є GPT-4 який нас скоро всіх замінить))

Дякс, а приклади з Поттером то взагалі топ

Думаю що ще довго будемо користуватися Elasticsearch та іншими системами з відкритим кодом, використання зовнішнього API поки що дійсно повільніше + залишається питання надійності

А ще питання вартості та доцільності відповідно

Зате тепер зрозуміло до чого все рухається. Зрозуміло по AI було й раніше, але вже є практичне застосування, ось хоча б той самий пошук (але поки що обмежено)

Спробував цю статтю пошагово виконувати в колабі, підміняв свої запити. Працює. Дякую за цікавинку. А де шукати датасети усілякі?

columns = [’title’, ’overview’] а звідки воно знає назви цих колонок? це ключові слова чи воно таке розумне?

Подивись код в колабі: colab.research.google.com/...​h6kJvyEGSYAmz?usp=sharing

Ми явно готуємо текст до embedding.

Дивлюсь на пітона

# Create a new column named adjusted.
my_dataframe["adjusted"] = my_dataframe["activity"] + 2

# Print the entire DataFrame
print(my_dataframe)

Чому двійку добавляєм а не 1?

(Напиши вірш про хлопчика, який вчить deep learning. З римою і поясненням що таке векторизація, щоб було схоже що це Альберт Энштейн написав) -> chatGPT

Векторизація — це те, що допомагає нам зрозуміти,
Як дані пов’язані між собою і що ми можемо з ними робити,
Коли ми перекладаємо їх у числа та вектори,
То отримуємо готовий інструмент для обробки даних напевнено.

Люди, які радіють можливостям штучного інтелекту, думаючи, що він полегшить їхню роботу, насправді не уявляють, що через 10-20 років ШІ виконуватиме 95% їхньої роботи та зробить непотрібними їх професії.

Через 10-20 лет люди будут радоваться и молится на ИИ, т.к. депопуляция развитых стран достигнет необратимого состояния.

ШІ і так давно з нами в пошукових системах. Це інструмент.

не думаю что через 10-20 лет что-то изменится, ml тупиковая ветка для ai, ml его только имитирует, не более того. но да, потенциально он может заменить профессии, которые не особо нужны и сейчас, только вот не для того эти профессии существуют чтобы их заменяли автоматизацией =\

ШІ

сначала подумал что речь о ch’i

Справа генерала Лудда непереможна!

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

Підписатись на коментарі