Я решил «на скорую руку» собрать локальную РАГ (генерацию восстановления аугментации), которая будет находить термины из словаря Ожегова. Изучив Интернет, я понял. Все сводится к рецепту (упрощенная интерпретация):

  1. Берем нужный нам текст и разрезаем его на части

  2. Используя «embedder», мы превращаем его во встраивание.

  3. Загрузка в векторную базу данных

  4. Объединение ChatGPT или альтернативы с использованием Langchain

  5. Я пишу подсказку

  6. Мы радуемся

План работы

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

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

Подготовка данных

Берем нужный нам текст и разрезаем его на части.

import re
import pandas as pd

with open("ozhegov.txt", mode="r", encoding="UTF-8") as file:
    text_lines = file.readlines()

def return_first_match(pattern, text):
    result = re.findall(pattern,text)
    result = result[0] if result else ""
    return result

data = []

for line in text_lines:

    title = return_first_match(r"^[а-яА-Я]{2,}(?=,)", line)
    text = return_first_match(r"\.\s([А-Я]+.*)\n", line)

    if(len(title) > 3):
        data.append(
            {
                "title": title,
                "text" : text
            })

dataset = pd.DataFrame(data)
dataset.to_csv('ozhegov_dataset.csv')
dataset.head()

заголовок

текст

0

АБАЖУР

Колпачок для лампы, лампы. Грин А. 23:00….

1

АБАЗИНСКИЙ

Относительно абазин, их языка, их национальности…

2

АБАЗИНЫ

Жители Карачаево-Черкесии и Адыгеи…

3

АББАТ

Настоятель мужского католического монастыря. 2…

4

АББАТСТВО

Католический монастырь.

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

#бывают пустые в моем датасете
dataset = dataset[dataset['title'].str.len() > 0]
dataset = dataset[dataset['text'].str.len() > 0]
dataset = dataset.sample(n=1000)

dataset.astype({"text": str, "title": str})
dataset.info(show_counts=True)

dataset.head()
<class 'pandas.core.frame.DataFrame'>
Index: 1000 entries, 28934 to 16721
Data columns (total 3 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   Unnamed: 0  1000 non-null   int64 
 1   title       1000 non-null   object
 2   text        1000 non-null   object
dtypes: int64(1), object(2)
memory usage: 31.2+ KB

Безымянный: 0

заголовок

текст

28934

Утолщение

Утолщенная область на чем-то. У. багажник. Американский корабль.

30193

ЧЕРНАЯ СОТЕНЬ

В России в начале. 20 век: член шовинистической группы…

14378

НЕВОЗМОЖНЫЙ

Тот, кого невозможно преодолеть. Неотразимый…

27420

ТЕРМАЛЬНЫЙ

Что касается применения тепловой энергии в…

27021

СХЕМА

Присутствовать (-влять) в виде диаграммы (в 2-х значениях)…

ЧИТАТЬ   Поставщик вычислений в области искусственного интеллекта CoreWeave стоимостью 19 миллиардов долларов открывает европейскую штаб-квартиру в Лондоне и планирует построить 2 центра обработки данных в Великобритании | TechCrunch

Превратите это в интеграцию

Давно не выбирал «Embedder», пользовался оценочными предложениями русскоязычных кодировщиков

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

Для чистоты эксперимента я решил взять 5 лучших моделей и 3 различные функции расстояния, доступные в Цветность

models = [
    "intfloat/multilingual-e5-large",
    "sentence-transformers/paraphrase-multilingual-mpnet-base-v2",
    "symanto/sn-xlm-roberta-base-snli-mnli-anli-xnli",
    "cointegrated/LaBSE-en-ru",
    "sentence-transformers/LaBSE"
]

distances = [
    "l2",
    "ip",
    "cosine"
]

Цветность готова

Мы ставим необходимое семя пакеты

%pip install -U sentence-transformers ipywidgets chromadb chardet charset-normalizer
При установке возникла ошибка, решение есть в самой ошибке

СОВЕТ: Эта ошибка могла возникнуть из-за того, что в этой системе не включена поддержка Windows Long Path. Информацию о том, как это включить, вы можете найти на сайте

https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=powershell#enable-long-paths-in-windows-10-version-1607-and- позже

Давайте запустим Chroma в Docker

docker pull chromadb/chroma
docker run -p 8000:8000 chromadb/chroma

Особенности работы с Chroma

Мы определяем функции, необходимые для создания и удаления коллекций. А еще функция поиска, в которой берем запись с минимальным расстоянием + добавляем данные по индексу из датасета. Индексы базы данных равны индексам набора данных.

from chromadb.utils import embedding_functions
import chromadb
chroma_client = chromadb.HttpClient(host="localhost", port=8000)

def create_collection(model_name, distance):
    
    chroma_client = chromadb.HttpClient(host="localhost", port=8000)

    sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=model_name)
    
    #в этом эксперименте не будем использовать, нам нужно найти термин
    #text_collection = chroma_client.create_collection(name="text", embedding_function=sentence_transformer_ef)
    
    title_collection = chroma_client.create_collection(name="title", embedding_function=sentence_transformer_ef, metadata={"hnsw:space": distance})

    ids = list(map(str, dataset.index.values.tolist()))
    #text_collection.add(ids = ids, documents=dataset["text"].tolist())
    title_collection.add(ids = ids, documents=dataset["title"].tolist())

    return title_collection

def delete_collection():
    chroma_client.delete_collection("title")


def query_collection(collection, query, max_results, dataframe, model_name, distance):
    results = collection.query(query_texts=query, n_results=max_results, include=['distances']) 
    #print(results)
    df = pd.DataFrame({
                'id':results['ids'][0], 
                'score':list(map(float,results['distances'][0])),
                'query': query,
                'title': dataframe[dataframe.index.isin(list(map(int,results['ids'][0])))]['title'],
                'content': dataframe[dataframe.index.isin(list(map(int,results['ids'][0])))]['text'],
                'model_name': model_name,
                'distance': distance
                })
    
    # Забираем с минимальной дистанцией, значит он ближе и больше похож
    df = df[df.score == df.score.min()]
    df['is_found'] = df.apply(lambda row: row.query == row.title, axis=1)
    
    return df

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

Формируем тестовый набор данных из 100 случайных элементов из набора данных, загруженного в Chroma.

test_dataset = dataset.sample(n=100)
test_dataset.head()
test_results = pd.DataFrame()

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

for model in models:
    for distance in distances:
        print(f"{model} - {distance}")
        try:
            delete_collection()
        except Exception as ex:
            print(f"delete_collection error: {ex}")

        collection = create_collection(model, distance)

        for title in test_dataset["title"].tolist():
            test_results = test_results._append(query_collection(
            collection=collection,
            query=title,
            max_results=5,
            dataframe=dataset,
            model_name=model,
            distance=distance))

            print(f"{len(test_results)}")

        

test_results.to_csv("results_ozhegov2.csv")
test_results.head()

идентификатор

счет

запрос

заголовок

содержание

название модели

расстояние

найден

10363

10363

1.315708e-12

ИЗ КОРНИШОНА

ИЗ КОРНИШОНА

Небольшие незрелые огурцы, предназначенные для…

intfloat/многоязычный-e5-большой

л2

истинный

8566

8566

7.252605е-13

ИММИГРАНТ

ИММИГРАНТ

Человек, который куда-то иммигрировал. Он в моем…

intfloat/многоязычный-e5-большой

л2

истинный

12175

17352

1.157366e-12

ПЕНСИЯ

МЕНСТРУАЦИЯ

Ежемесячные кровотечения из матки женщины (…

intfloat/многоязычный-e5-большой

л2

ФАЛЬШИВЫЙ

18297

11029

7.939077е-13

ПУЧКА

ЗАПУТАТЬ

Идти, не зная дороги, бродить. П. через лес.

intfloat/многоязычный-e5-большой

л2

ФАЛЬШИВЫЙ

14052

5394

1.371903e-12

ДЕСЯТИЛЕТИЕ

ВОСКРЕСЕНЬЕ

Единица времени, равная семи дням…

intfloat/многоязычный-e5-большой

л2

ФАЛЬШИВЫЙ

ЧИТАТЬ   В охране Зеленского замечен «инопланетянин»

Давайте посмотрим на результаты

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

finally_result = pd.DataFrame()
for model in models:
    for distance in distances:
        df = test_results.loc[test_results['model_name'].str.contains(model) == True]
        df = df.loc[df['distance'].str.contains(distance) == True]

        finally_result = finally_result._append(pd.DataFrame({
                'founded': [len(df[df['is_found'] == True])],
                'model_name': [model],
                'distance': [distance]
                }))
        
finally_result.head(15)

основанный на

название модели

расстояние

24

intfloat/многоязычный-e5-большой

л2

24

intfloat/многоязычный-e5-большой

IP адрес

24

intfloat/многоязычный-e5-большой

косинус

17

преобразователи предложений/многоязычный-перефраз-…

л2

0

преобразователи предложений/многоязычный-перефраз-…

IP адрес

19

преобразователи предложений/многоязычный-перефраз-…

косинус

23

symanto/sn-xlm-roberta-base-snli-mnli-anli-xnli

л2

25

symanto/sn-xlm-roberta-base-snli-mnli-anli-xnli

IP адрес

21

symanto/sn-xlm-roberta-base-snli-mnli-anli-xnli

косинус

14

коинтегрированный/LaBSE-fr-ru

л2

14

коинтегрированный/LaBSE-fr-ru

IP адрес

14

коинтегрированный/LaBSE-fr-ru

косинус

14

преобразователи предложений/LaBSE

л2

14

преобразователи предложений/LaBSE

IP адрес

14

преобразователи предложений/LaBSE

косинус

Заключение

«Быстро» сформировать местную РАГ для работы с условиями и выработки готового рецепта пока не удалось.

Ток ~25% — хорошей точностью сложно назвать.

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

  1. Используйте гибридный поиск с BM25, не все векторные базы данных поддерживают его.

    1. Цветность — в разработке

    2. Сосновая шишка – общедоступный предварительный просмотр — недоступно в Docker — запрос функции

    3. Милвус — в разработке

    4. Weaviate — поддерживает

    5. Qdrant — поддерживает

    6. Elasticsearch — поддерживает

    7. Открытый поиск – поддерживает

  2. Подключите Postgres + BM25 в качестве второго сборщика и ищите сразу в двух — вроде так себе + дублирующаяся информация…

  3. Настройте модель, но она далеко не «быстрая»

  4. Работа с текстом (лемматизация, нормализация и т.п.) — очень сомневаюсь, что это поможет.

Источник

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

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

Source

От admin