Добавьте понимание естественного языка в любое приложение

Это обновленная версия оригинальной статьи.

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

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

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

Представляем txtai

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

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

Базы данных внедрения могут существовать сами по себе и/или служить мощным источником знаний для подсказок модели большого языка (LLM).

Ниже приводится краткое изложение ключевых особенностей:

  • 🔎 Векторный поиск с помощью SQL, хранилище объектов, тематическое моделирование, анализ графов и мультимодальное индексирование.
  • 📄 Создавайте вложения для текста, документов, аудио, изображений и видео.
  • 💡 Конвейеры, основанные на языковых моделях, которые запускают подсказки LLM, ответы на вопросы, маркировку, транскрипцию, перевод, обобщение и многое другое.
  • ↪️️ Рабочие процессы для объединения конвейеров и агрегирования бизнес-логики. Процессы txtai могут быть простыми микросервисами или многомодельными рабочими процессами.
  • ⚙️ Создавайте с помощью Python или YAML. Доступны привязки API для JavaScript, Java, Rust и Go.
  • ☁️ Запускайте локально или масштабируйте с помощью оркестрации контейнеров.

txtai построен на Python 3.8+, Трансформаторах обнимающих лиц, Трансформаторах предложений и FastAPI. txtai имеет открытый исходный код под лицензией Apache 2.0.

Установите и запустите txtai

txtai можно установить через pip или Docker. Ниже показано, как установить через pip.

pip install txtai

Семантический поиск

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

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

from txtai import Embeddings

# Works with a list, dataset or generator
data = [
  "US tops 5 million confirmed virus cases",
  "Canada's last fully intact ice shelf has suddenly collapsed, forming a Manhattan-sized iceberg",
  "Beijing mobilises invasion craft along coast as Taiwan tensions escalate",
  "The National Park Service warns against sacrificing slower friends in a bear attack",
  "Maine man wins $1M from $25 lottery ticket",
  "Make huge profits without work, earn up to $100,000 a day"
]

# Create an embeddings
embeddings = Embeddings(path="sentence-transformers/nli-mpnet-base-v2")

# Create an index for the list of text
embeddings.index(data)

print("%-20s %s" % ("Query", "Best Match"))
print("-" * 50)

# Run an embeddings search for each query
for query in ("feel good story", "climate change", 
    "public health story", "war", "wildlife", "asia",
    "lucky", "dishonest junk"):
  # Extract uid of first result
  # search result format: (uid, score)
  uid = embeddings.search(query, 1)[0][0]

  # Print text
  print("%-20s %s" % (query, data[uid]))

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

Обновления и удаления

Для вложений поддерживаются обновления и удаления. Операция upser вставит новые данные и обновит существующие данные.

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

# Run initial query
uid = embeddings.search("feel good story", 1)[0][0]
print("Initial: ", data[uid])

# Create a copy of data to modify
udata = data.copy()

# Update data
udata[0] = "See it: baby panda born"
embeddings.upsert([(0, udata[0], None)])

uid = embeddings.search("feel good story", 1)[0][0]
print("After update: ", udata[uid])

# Remove record just added from index
embeddings.delete([0])

# Ensure value matches previous value
uid = embeddings.search("feel good story", 1)[0][0]
print("After delete: ", udata[uid])
Initial:  Maine man wins $1M from $25 lottery ticket
After update:  See it: baby panda born
After delete:  Maine man wins $1M from $25 lottery ticket

Упорство

Вложения можно сохранить в хранилище и перезагрузить.

embeddings.save("index")

embeddings = Embeddings()
embeddings.load("index")

uid = embeddings.search("climate change", 1)[0][0]
print(data[uid])
Canada's last fully intact ice shelf has suddenly collapsed, forming a
Manhattan-sized iceberg

Гибридный поиск

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

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

# Create an embeddings
embeddings = Embeddings(
  hybrid=True,
  path="sentence-transformers/nli-mpnet-base-v2"
)

# Create an index for the list of text
embeddings.index(data)

print("%-20s %s" % ("Query", "Best Match"))
print("-" * 50)

# Run an embeddings search for each query
for query in ("feel good story", "climate change", 
    "public health story", "war", "wildlife", "asia",
    "lucky", "dishonest junk"):
  # Extract uid of first result
  # search result format: (uid, score)
  uid = embeddings.search(query, 1)[0][0]

  # Print text
  print("%-20s %s" % (query, data[uid]))

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

# Create an embeddings
embeddings = Embeddings(keyword=True)

# Create an index for the list of text
embeddings.index(data)

print(embeddings.search("feel good story"))
print(embeddings.search("lottery"))
[]
[(4, 0.5234998733628726)]

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

Хранение контента

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

Хранилище контента добавляет связанную базу данных (например, SQLite, DuckDB), в которой хранятся связанные метаданные с векторным индексом. Текст документа, дополнительные метаданные и дополнительные объекты можно хранить и извлекать прямо вместе с индексированными векторами.

# Create embeddings with content enabled.
# The default behavior is to only store indexed vectors.
embeddings = Embeddings(
  path="sentence-transformers/nli-mpnet-base-v2",
  content=True,
  objects=True
)

# Create an index for the list of text
embeddings.index(data)

print(embeddings.search("feel good story", 1)[0]["text"])
Maine man wins $1M from $25 lottery ticket

Единственное изменение выше — установка флага content в значение True. Это позволяет хранить текст и метаданные (если они предусмотрены) вместе с индексом. Обратите внимание, как текст извлекается прямо из результата запроса!

Давайте добавим немного метаданных.

Запрос с помощью SQL

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

# Create an index for the list of text
embeddings.index([{"text": text, "length": len(text)} for text in data])

# Filter by score
print(embeddings.search("select text, score from txtai where similar('hiking danger') and score >= 0.15"))

# Filter by metadata field 'length'
print(embeddings.search("select text, length, score from txtai where similar('feel good story') and score >= 0.05 and length >= 40"))

# Run aggregate queries
print(embeddings.search("select count(*), min(length), max(length), sum(length) from txtai"))
[{'text': 'The National Park Service warns against sacrificing slower friends in a bear attack', 'score': 0.3151373863220215}]
[{'text': 'Maine man wins $1M from $25 lottery ticket', 'length': 42, 'score': 0.08329027891159058}]
[{'count(*)': 6, 'min(length)': 39, 'max(length)': 94, 'sum(length)': 387}]

В приведенном выше примере добавляется простое дополнительное поле «длина текста».

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

Хранилище объектов

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

import urllib

from IPython.display import Image

# Get an image
request = urllib.request.urlopen("https://raw.githubusercontent.com/neuml/txtai/master/demo.gif")

# Upsert new record having both text and an object
embeddings.upsert([("txtai", {"text": "txtai executes machine-learning workflows to transform data and build AI-powered semantic search applications.", "object": request.read()}, None)])

# Query txtai for the most similar result to "machine learning" and get associated object
result = embeddings.search("select object from txtai where similar('machine learning') limit 1")[0]["object"]

# Display image
Image(result.getvalue(), width=600)

Тематическое моделирование

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

# Create embeddings with a graph index
embeddings = Embeddings(
  path="sentence-transformers/nli-mpnet-base-v2",
  content=True,
  functions=[
    {"name": "graph", "function": "graph.attribute"},
  ],
  expressions=[
    {"name": "category", "expression": "graph(indexid, 'category')"},
    {"name": "topic", "expression": "graph(indexid, 'topic')"},
  ],
  graph={
    "topics": {
      "categories": ["health", "climate", "finance", "world politics"]
    }
  }
)

embeddings.index(data)
embeddings.search("select topic, category, text from txtai")
[{'topic': 'us_cases_million_confirmed',
  'category': 'finance',
  'text': 'US tops 5 million confirmed virus cases'},
 {'topic': "canada's_iceberg_suddenly_forming",
  'category': 'climate',
  'text': "Canada's last fully intact ice shelf has suddenly collapsed, forming a Manhattan-sized iceberg"},
 {'topic': 'coast_taiwan_mobilises_escalate',
  'category': 'world politics',
  'text': 'Beijing mobilises invasion craft along coast as Taiwan tensions escalate'}]

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

Категории тем также создаются, как показано выше.

Субиндексы

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

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

# Create embeddings with subindexes
embeddings = Embeddings(
  content=True,
  defaults=False,
  indexes={
    "keyword": {
      "keyword": True
    },
    "dense": {
      "path": "sentence-transformers/nli-mpnet-base-v2"
    }
  }
)
embeddings.index(data)
embeddings.search("feel good story", limit=1, index="keyword")
[]
embeddings.search("feel good story", limit=1, index="dense")
[{'id': '4',
  'text': 'Maine man wins $1M from $25 lottery ticket',
  'score': 0.08329027891159058}]

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

LLM оркестровка

txtai — это универсальная база данных для встраивания. Это единственная векторная база данных, которая также поддерживает разреженные индексы, графовые сети и реляционные базы данных со встроенной поддержкой SQL. В дополнение к этому txtai поддерживает оркестровку LLM.

Конвейер экстрактора — это вариант txtai, основанный на технологии дополненной генерации (RAG). Этот конвейер извлекает знания из контента путем объединения подсказки, хранилища контекстных данных и генеративной модели.

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

import torch
from txtai.pipeline import Extractor

def prompt(question):
  return [{
    "query": question,
    "question": f"""
Answer the following question using the context below.
Question: {question}
Context:
"""
}]

# Create embeddings
embeddings = Embeddings(
  path="sentence-transformers/nli-mpnet-base-v2",
  content=True,
  autoid="uuid5"
)

# Create an index for the list of text
embeddings.index(data)

# Create and run extractor instance
extractor = Extractor(
  embeddings,
  "google/flan-t5-large", 
  torch_dtype=torch.bfloat16, 
  output="reference"
)
extractor(prompt("What country is having issues with climate change?"))[0]
{'answer': 'Canada', 'reference': 'da633124-33ff-58d6-8ecb-14f7a44c042a'}

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

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

uid = extractor(prompt("What country is having issues with climate change?"))[0]["reference"]
embeddings.search(f"select id, text from txtai where id = '{uid}'")
[{'id': 'da633124-33ff-58d6-8ecb-14f7a44c042a',
  'text': "Canada's last fully intact ice shelf has suddenly collapsed, forming a Manhattan-sized iceberg"}]

Вывод LLM также может выполняться автономно.

from txtai.pipeline import LLM

llm = LLM("google/flan-t5-large", torch_dtype=torch.bfloat16)
llm("Where is one place you'd go in Washington, DC?")
national museum of american history

Рабочие процессы языковой модели

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

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

# Embeddings instance
writable: true
embeddings:
  path: sentence-transformers/nli-mpnet-base-v2
  content: true
  functions:
    - {name: translation, argcount: 2, function: translation}

# Translation pipeline
translation:

# Workflow definitions
workflow:
  search:
    tasks:
      - search
      - action: translation
        args:
          target: fr
        task: template
        template: "{text}"

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

from txtai import Application

# Build index
app = Application("embeddings.yml")
app.add(data)
app.index()

# Run workflow
list(app.workflow(
  "search", 
  ["select text from txtai where similar('feel good story') limit 1"]
))
['Maine homme gagne $1M à partir de $25 billet de loterie']

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

app.search("select translation(text, 'fr') text from txtai where similar('feel good story') limit 1")
[{'text': 'Maine homme gagne $1M à partir de $25 billet de loterie'}]

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

sequences:
  path: google/flan-t5-large
  torch_dtype: torch.bfloat16

workflow:
  chain:
    tasks:
      - task: template
        template: Translate '{statement}' to {language} if it's English
        action: sequences
      - task: template
        template: What language is the following text? {text}
        action: sequences
inputs = [
  {"statement": "Hello, how are you", "language": "French"},
  {"statement": "Hallo, wie geht's dir", "language": "French"}
]

app = Application("workflow.yml")
list(app.workflow("chain", inputs))
['French', 'German']

Подведение итогов

НЛП развивается быстрыми темпами. То, что было невозможно еще год назад, теперь возможно. В этой статье представлена ​​txtai — универсальная база данных для встраивания. Возможности безграничны, и мы с нетерпением ждем возможности увидеть, что можно построить на основе txtai!

Посетите ссылки ниже, чтобы узнать больше.

ГитХаб | Документация | "Примеры"