Этот сайт использует файлы cookies. Продолжая просмотр страниц сайта, вы соглашаетесь с использованием файлов cookies. Если вам нужна дополнительная информация, пожалуйста, посетите страницу Политика файлов Cookie
Subscribe
Прямой эфир
Cryptocurrencies: 9884 / Markets: 82654
Market Cap: $ 2 214 496 913 796 / 24h Vol: $ 67 378 639 821 / BTC Dominance: 52.789525927228%

Н Новости

Как общаться с базой знаний на естественном языке с помощью LLM и объективно оценить работу полученной системы

Привет, Хабр! Меня зовут Даниил, работаю в ML-отделе Doubletapp. В статье расскажу про особенности применения больших языковых моделей для оптимизации бизнес-процессов.

d5109e23b536f954b10ceb726fc92a5b.jpg

Большая языковая модель (LLM) — это тип языковой модели, который способен распознавать и генерировать осмысленные тексты, а также другие сложные типы данных (например, код). Такого рода модели обучаются на огромных массивах данных, чаще всего собранных из открытых источников. За счет объема обучающей выборки, а также высокого числа параметров они занимают лидирующие строчки в бенчмарках по различным задачам (например, суммаризация, QA, генерация кода и т. д.)

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

Такая система ответов на вопросы с использованием фактической информации называется RAG (Retrieval Augmented Generation). Она может использоваться в различных сценариях, например:
1. Персонализированный ассистент по базе знаний компании для онбординга новых сотрудников.
2. Оптимизация линий поддержки за счет более быстрых и точных ответов на ее первой линии.
3. Помощник в обучении на онлайн-курсах и т. п.

Данная статья состоит из двух частей:
1. мы рассмотрим построение RAG-системы на основе библиотеки langchain;
2. объективно оценим работоспособность созданной системы, используя синтетические данные на русском языке с помощью фреймворка RAGAs.

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

Любой RAG, как понятно из расшифровки, состоит из трех этапов:
1. Retrieval — поиск наиболее релевантной информации в нашей базе знаний за счет семантического поиска, пересечения слов и т. п.
2. Augment — добавление найденной информации (контекста) в prompt для LLM вместе с запросом пользователя.
3. Generate — генерация ответа моделью с учетом контекста.

5c81a42cb94dfa2729249e8a8ecdbd50.png

Retrieval

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

Качество извлечения зависит от нескольких составляющих.

Данные

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

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

Разбиение данных

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

Существует множество стратегий разбиения текста — одной из самых популярных является деление на фиксированные части (chunk’и), часто в таких случаях имеет смысл делать разбиение с перекрытием, чтобы не потерять мысль на середине предложения. Другой стратегией может быть тематическое разбиение, например, по абзацам или заголовкам тем.

В целом, размер chunk’a может быть определен исходя из логики ваших данных, но этот параметр может варьироваться, и точно стоит попробовать несколько вариантов. Trade-off получается таким: меньшие по размеру кусочки содержат более конкретную мысль, а соответственно лучше обнаруживаются нашим поиском, но, в свою очередь, они могут ухудшить процесс генерации из-за отсутствия окружающего их контекста.

Поиск

Поиск можно формально разделить на два вида: векторный и поиск по ключевым словам. Векторный поиск — это метод поиска информации, в котором тексты представляются в виде векторов, полученных с помощью ML-модели. Затем происходит поиск наиболее близкого вектора. Векторный поиск сейчас достаточно популярен, но не является панацеей, так как его качество сильно зависит от модели, которую мы используем для создания эмбеддингов. Стоит попробовать key-word поиск, например, TF-IDF или BM-25 и сравнить их возможности с векторными вариантами или же использовать гибридный поиск. Так как похожая выдача не всегда является релевантной, то эффективной стратегией может стать фильтрация по метаданным. Например, если наша база знаний представляет собой отзывы на фильмы, а пользователь хочет найти только те киноленты, что были выпущены после 2000 года или те, что имеют рейтинг выше 8, мы можем сделать это с помощью SelfQueryRetriever. Он извлечет метаданные из запроса и отфильтрует по ним результаты.

Другой интересной стратегией является re-writing вопроса пользователя, что позволяет улучшить извлечение данных за счет исправления плохо сформулированных вопросов пользователя.

Создаем Retriever

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

from typing import List, Tuple
from langchain.document_loaders import TextLoader
from langchain.text_splitter import MarkdownHeaderTextSplitter



# Text loader and title splitter
def load_and_split_markdown(filepath: str, splitter: List[Tuple[str, str]]):
    loader = TextLoader(filepath)
    docs = loader.load()

    markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=splitter)
    md_header_splits = markdown_splitter.split_text(docs[0].page_content)
    return md_header_splits

Чтобы создать эмбеддинги для chunk’ов используется модель "text-embedding-ada-002" от OpenAI ввиду удобства использования API и высокого качества эмбеддингов.

Для хранения полученных векторов хорошо подходит интегрированная в Langchain Chroma DB. Хочу также отметить, что langchain может работать с множеством векторных баз данных, как open source (ChromaDB, LanceDB, Faiss), так и с платными аналогами — Weaviate, Pinecone, etc. Для нашего примера бесплатной ChromaDB будет вполне достаточно.

В качестве retriever’a выступает EnsembleRetriever, состоящий из поиска по векторам, key-word поиска BM-25 и вышеупомянутого SelfQueryRetriever’a в пропорциях 0.6 / 0.25 / 0.15.

from langchain.llms import OpenAI
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.retrievers import BM25Retriever, EnsembleRetriever
from langchain.retrievers.self_query.base import SelfQueryRetriever


def get_retriever(splits, bm25_k, mmr_k, 
                  mmr_fetch_k, metadata_field_info, 
                  document_content_description):
    llm = OpenAI(temperature=0)

    # Embeddings for vector search
    embedding = OpenAIEmbeddings()

    # DB for our vectors
    vectorstore = Chroma.from_documents(documents=splits, embedding=embedding)
    
    # Key-word retriever
    bm25_retriever = BM25Retriever.from_documents(splits)
    bm25_retriever.k = bm25_k
    
    # Vector-based retriever
    mmr_retriever = vectorstore.as_retriever(
        search_type="mmr", search_kwargs={'k': mmr_k, 'fetch_k': mmr_fetch_k}
    )
    
    # Self Query Retriever
    self_retriever = SelfQueryRetriever.from_llm(
        llm, 
        vectorstore, 
        document_content_description, 
        metadata_field_info, 
        verbose=True
    )



    # Retriever combination
    ensemble_retriever = EnsembleRetriever(
        retrievers=[self_retriever, bm25_retriever, mmr_retriever], 
        weights=[0.15, 0.25, 0.6]
    )

    return ensemble_retriever

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

CONTENT_DESCRIPTION: Final = "Описание банковских продуктов"


METADATA_INFO: Final = [
        AttributeInfo(
            name="Заголовок",
            description="Часть документа, откуда был взят текст",
            type="string or list[string]",
        ),
    ]

Augment

На данном этапе мы формируем запрос для нашей нейросети, который состоит из найденного нами на предыдущем шаге контекста и prompt’a. Prompt — своего рода инструкция для LLM, которая говорит сети, что нужно сделать с контекстом, который мы в нее подаем.

Варьирование prompt’a

Обычно в качестве базового prompt’a используется что-то наподобие «Ответь на вопрос, используя контекст ниже», но мы можем изменить его, добавив больше деталей, что может помочь модели ответить более точно. Например, приведенный выше prompt может быть дополнен: «Ты являешься помощником по дебетовой карте банка. Опирайся только на информацию, приведенную ниже, если не знаешь ответ, ответь “не знаю”».

Дополнение контекста

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

Делаем аугментацию

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

PROMT_TEMPLATE: Final = """
    Вы помощник по продуктам банка bank_name и отвечаете на вопросы клиентов.
    Используйте фрагменты полученного контекста, чтобы ответить на вопрос.
    Если вы не знаете ответа, то скажите, что не знаете, не придумывайте ответ.
    Используйте максимум три предложения и будьте краткими.\n
    Вопрос: {question} \n
    Контекст: {context} \n
    Ответ:
    """

Generation

Генерация является последним этапом в пайплайне и заключается в подаче prompt’a и контекста в модель.

Различные модели

В качестве экспериментов стоит попробовать различные LLM, в зависимости от специфики данных, языка и т. п. одни модели будут работать лучше других. Также могут быть дополнительные требования в виде on-premise развертывания, что оставляет нас только с open source моделями.

Приведу интересное сравнение моделей в задаче RAG, которое увидел на Хабре.

Fine-tuning модели

Одним из вариантов повышения качества работы пайплайна является fine-tuning LLM на том домене, который используется в базе знаний. Например, можно применить LoRA/QLoRA подходы.

Завершаем pipeline

В качестве модели для генерации была выбрана модель GPT 3.5 Turbo, так как она обеспечивает достаточно хорошее качество генерации при более низкой стоимости (например, в сравнении с GPT-4).

# Setting up the LLM
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)

system_message_prompt = SystemMessagePromptTemplate.from_template(Settings.PROMT_TEMPLATE)
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt])

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)


# Loading texts
docs = load_and_split_markdown('data/docs/bank_name_docs.md', Settings.HEADERS_TO_SPLIT)

# Setting up the retriever
ensemble_retriever = get_retriever(
    docs, 
    Settings.BM25_K, Settings.MMR_K, Settings.MMR_FETCH_K, 
    Settings.METADATA_INFO, Settings.CONTENT_DESCRIPTION
)


# RAG pipeline
rag_chain_from_docs = (
    {
        "context": lambda input: format_docs(input["documents"]),
        "question": itemgetter("question"),
    }
    | chat_prompt
    | llm
    | StrOutputParser()
)

rag_chain_with_source = RunnableParallel(
    {"documents": ensemble_retriever, "question": RunnablePassthrough()}
) | {
    "documents": lambda input: [doc.metadata for doc in input["documents"]],
    "answer": rag_chain_from_docs,
}

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

Для финального штриха я добавил ChatGPT-подобный интерфейс для комфортного взаимодействия с RAG’ом. Вот что получилось:

f6c1e717275070fb49aa27a736cbfcb3.png

Оценка RAG

Теперь, зная как построить RAG, нужно ответить себе на вопрос: насколько хорошо он работает?

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

RAGAs — open source фреймворк, разработанный для оценки компонентов пайплайна без помощи человека. Он позволяет создать тестовый датасет и получить оценку построенного нами RAG’a.

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

RAGAs принимает следующие входные параметры:
1. question — вопрос пользователя, который он подает в RAG;
2. answer — ответ, сгенерированный нашим пайплайном;
3. contexts — контексты, использовавшиеся при ответе на вопрос;
4. ground_truth — верный ответ на вопрос пользователя.

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

19750e9fbfd0511d16224b46caa481a6.png

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

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

Context precision
Численная мера того, насколько полученный контекст соответствует информации, необходимой для ответа на вопрос. Данная метрика считается как отношение корректно извлеченных кусочков относительно всего их количества. Она позволяет найти оптимальный размер chunk’a при разбиении текста.

Context recall — измеряет то, насколько релевантный контекст был найден retriver’ом относительно ground_truth ответов и единственная метрика, которая их использует.

Оценим pipeline

Для начала необходимо сгенерировать синтетический датасет с вопросами, основанными на базе знаний. В RAGAs есть удобный класс — TestGenerator, который позволяет создать датасет в несколько строчек. Однако внутри него зашиты prompt’ы на английском, и чтобы получать ответы на русском, пришлось сделать уточнения ко всем prompt’ам, используемым этим классом.

Для этого я в каждый prompt дописал фразу: «Твоя задача сформулирована на английском, но ответ должен быть на языке контекста», а затем, наследуясь от класса TestGenerator, переопределил функции, использующие prompt’ы.

SEED_QUESTION = HumanMessagePromptTemplate.from_template(
    """\
Your instructions are given in English but the answer should be in the same language as the context.
Your task is to formulate a question from given context satisfying the rules given below:
    1.The question should make sense to humans even when read without the given context.
    2.The question should be fully answered from the given context.
    3.The question should be framed from a part of context that contains important information. It can also be from tables,code,etc.
    4.The answer to the question should not contain any links.
    5.The question should be of moderate difficulty.
    6.The question must be reasonable and must be understood and responded by humans.
    7.Do no use phrases like 'provided context',etc in the question
    8.Avoid framing question using word "and" that can be decomposed into more than one question.
    9.The question should not contain more than 10 words, make of use of abbreviation wherever possible.
    
context:{context}
"""  # noqa: E501
)

Если интересно подробно разобраться в том, как работает цикл построения датасета в TestGenerator, рекомендую ознакомиться с вот этой статьей.

from langchain.document_loaders import TextLoader
from langchain.embeddings import OpenAIEmbeddings
from langchain.chat_models import ChatOpenAI
from ragas.llms import LangchainLLM
from generator import RussianTestGenerator

loader = TextLoader('data/docs/bank_name_docs.md')
docs = loader.load()


# Add custom llms and embeddings
generator_llm = LangchainLLM(llm=ChatOpenAI(model="gpt-3.5-turbo"))
critic_llm = LangchainLLM(llm=ChatOpenAI(model="gpt-4"))
embeddings_model = OpenAIEmbeddings()

# Change resulting question type distribution
testset_distribution = {
    "simple": 0.25,
    "reasoning": 0.25,
    "multi_context": 0.25,
    "conditional": 0.25,
}


test_generator = RussianTestGenerator(
    generator_llm=generator_llm,
    critic_llm=critic_llm,
    embeddings_model=embeddings_model,
    testset_distribution=testset_distribution
)

synth_data = test_generator.generate(docs, test_size=15).to_pandas()

После создания датасета необходимо получить ответы и сопутствующий контекст от RAG системы

answers = []
contexts = []

for query in tqdm(synth_data.question.tolist(), desc='Generation answers'):
    answers.append(rag_chain_with_source.invoke(query)['answer'])
    contexts.append([unicodedata.normalize('NFKD', docs.page_content) for docs in ensemble_retriever.get_relevant_documents(query)])

ground_truth = list(map(ast.literal_eval, synth_data.ground_truth.tolist()))

data = {
    "question": synth_data.question.tolist(),
    "answer": answers,
    "contexts": contexts,
    "ground_truths": ground_truth
}

dataset = Dataset.from_dict(data)

result = evaluate(
    dataset = dataset, 
    metrics=[
        context_precision,
        context_recall,
        faithfulness,
        answer_relevancy,
    ],
).to_pandas()

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

dc32aa65512caf43751965db662eeb8a.png

Средние значения метрик на датасете следующие:
context_precision — 0,586185516
context_recall — 0,855654762
faithfulness — 0,852083333
answer_relevancy — 0,836044521

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

Полный код примера вместе с данными и оценкой находится на github.

Сегодня мы рассмотрели step-by-step создание RAG системы и то, какие тонкости существуют в разработке каждого этапа, а также получили численную оценку работы системы с помощью фреймворка RAGAs.

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

Познакомиться с ML-проектами Doubletapp можно на сайте компании.

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

Источник

  • 07.09.23 16:24 CherryTeam

    Cherry Team atlyginimų skaičiavimo programa yra labai naudingas įrankis įmonėms, kai reikia efektyviai valdyti ir skaičiuoti darbuotojų atlyginimus. Ši programinė įranga, turinti išsamias funkcijas ir patogią naudotojo sąsają, suteikia daug privalumų, kurie padeda supaprastinti darbo užmokesčio skaičiavimo procesus ir pagerinti finansų valdymą. Štai keletas pagrindinių priežasčių, kodėl Cherry Team atlyginimų skaičiavimo programa yra naudinga įmonėms: Automatizuoti ir tikslūs skaičiavimai: Atlyginimų skaičiavimai rankiniu būdu gali būti klaidingi ir reikalauti daug laiko. Programinė įranga Cherry Team automatizuoja visą atlyginimų skaičiavimo procesą, todėl nebereikia atlikti skaičiavimų rankiniu būdu ir sumažėja klaidų rizika. Tiksliai apskaičiuodama atlyginimus, įskaitant tokius veiksnius, kaip pagrindinis atlyginimas, viršvalandžiai, premijos, išskaitos ir mokesčiai, programa užtikrina tikslius ir be klaidų darbo užmokesčio skaičiavimo rezultatus. Sutaupoma laiko ir išlaidų: Darbo užmokesčio valdymas gali būti daug darbo jėgos reikalaujanti užduotis, reikalaujanti daug laiko ir išteklių. Programa Cherry Team supaprastina ir pagreitina darbo užmokesčio skaičiavimo procesą, nes automatizuoja skaičiavimus, generuoja darbo užmokesčio žiniaraščius ir tvarko išskaičiuojamus mokesčius. Šis automatizavimas padeda įmonėms sutaupyti daug laiko ir pastangų, todėl žmogiškųjų išteklių ir finansų komandos gali sutelkti dėmesį į strategiškai svarbesnę veiklą. Be to, racionalizuodamos darbo užmokesčio operacijas, įmonės gali sumažinti administracines išlaidas, susijusias su rankiniu darbo užmokesčio tvarkymu. Mokesčių ir darbo teisės aktų laikymasis: Įmonėms labai svarbu laikytis mokesčių ir darbo teisės aktų, kad išvengtų baudų ir teisinių problemų. Programinė įranga Cherry Team seka besikeičiančius mokesčių įstatymus ir darbo reglamentus, užtikrindama tikslius skaičiavimus ir teisinių reikalavimų laikymąsi. Programa gali dirbti su sudėtingais mokesčių scenarijais, pavyzdžiui, keliomis mokesčių grupėmis ir įvairių rūšių atskaitymais, todėl užtikrina atitiktį reikalavimams ir kartu sumažina klaidų riziką. Ataskaitų rengimas ir analizė: Programa Cherry Team siūlo patikimas ataskaitų teikimo ir analizės galimybes, suteikiančias įmonėms vertingų įžvalgų apie darbo užmokesčio duomenis. Ji gali generuoti ataskaitas apie įvairius aspektus, pavyzdžiui, darbo užmokesčio paskirstymą, išskaičiuojamus mokesčius ir darbo sąnaudas. Šios ataskaitos leidžia įmonėms analizuoti darbo užmokesčio tendencijas, nustatyti tobulintinas sritis ir priimti pagrįstus finansinius sprendimus. Pasinaudodamos duomenimis pagrįstomis įžvalgomis, įmonės gali optimizuoti savo darbo užmokesčio strategijas ir veiksmingai kontroliuoti išlaidas. Integracija su kitomis sistemomis: Cherry Team programinė įranga dažnai sklandžiai integruojama su kitomis personalo ir apskaitos sistemomis. Tokia integracija leidžia automatiškai perkelti atitinkamus duomenis, pavyzdžiui, informaciją apie darbuotojus ir finansinius įrašus, todėl nebereikia dubliuoti duomenų. Supaprastintas duomenų srautas tarp sistemų padidina bendrą efektyvumą ir sumažina duomenų klaidų ar neatitikimų riziką. Cherry Team atlyginimų apskaičiavimo programa įmonėms teikia didelę naudą - automatiniai ir tikslūs skaičiavimai, laiko ir sąnaudų taupymas, atitiktis mokesčių ir darbo teisės aktų reikalavimams, ataskaitų teikimo ir analizės galimybės bei integracija su kitomis sistemomis. Naudodamos šią programinę įrangą įmonės gali supaprastinti darbo užmokesčio skaičiavimo procesus, užtikrinti tikslumą ir atitiktį reikalavimams, padidinti darbuotojų pasitenkinimą ir gauti vertingų įžvalgų apie savo finansinius duomenis. Programa Cherry Team pasirodo esanti nepakeičiamas įrankis įmonėms, siekiančioms efektyviai ir veiksmingai valdyti darbo užmokestį. https://cherryteam.lt/lt/

  • 08.10.23 01:30 davec8080

    The "Shibarium for this confirmed rug pull is a BEP-20 project not related at all to Shibarium, SHIB, BONE or LEASH. The Plot Thickens. Someone posted the actual transactions!!!! https://bscscan.com/tx/0xa846ea0367c89c3f0bbfcc221cceea4c90d8f56ead2eb479d4cee41c75e02c97 It seems the article is true!!!! And it's also FUD. Let me explain. Check this link: https://bscscan.com/token/0x5a752c9fe3520522ea88f37a41c3ddd97c022c2f So there really is a "Shibarium" token. And somebody did a rug pull with it. CONFIRMED. But the "Shibarium" token for this confirmed rug pull is a BEP-20 project not related at all to Shibarium, SHIB, BONE or LEASH.

Для участия в Чате вам необходим бесплатный аккаунт pro-blockchain.com Войти Регистрация
Есть вопросы?
С вами на связи 24/7
Help Icon