使用 LLMs 來生成合成數據

作者:Kritin Vongthongsri

編譯:ronghuaiyang

導讀

如果我告訴你,現在有可能在幾分鐘內生成數千個高質量的測試案例,這些案例你過去可能要花費數週時間精心製作,你會怎麼想?

構建大規模、全面的數據集來測試 LLM 輸出可能是一個耗時、昂貴且充滿挑戰的過程,尤其是從零開始。但如果我告訴你,現在有可能在幾分鐘內生成數千個高質量的測試案例,這些案例你過去可能要花費數週時間精心製作,你會怎麼想?

合成數據生成利用 LLM 來創建高質量數據,無需手動收集、清理和標註大量數據集。藉助像 GPT-4 這樣的模型,現在可以以遠少於人工標記數據集所需的時間,合成出更全面、更多樣化的數據集,這些數據集可用於藉助一些 LLM 評估指標來爲 LLM(系統)設立基準。

在本文中,我將教你如何使用 LLM 生成合成數據集(例如,可用於評估 RAG 管道)。我們將探討:

感興趣了嗎?讓我們深入瞭解一下。

使用 LLM 的合成數據生成是什麼?

使用 LLM 的合成數據生成涉及到使用 LLM 來創建人造數據,通常是可用於訓練、微調甚至評估 LLM 自身的數據集。生成合成數據集不僅比搜索公共數據集更快,比人工註釋更便宜,而且結果質量更高,數據多樣性更大,這對於構建 LLM 評估框架至關重要。

這一過程始於合成查詢的創建,這些查詢是使用來自你的知識庫(通常以文檔形式)的上下文作爲事實依據生成的。生成的查詢隨後會經歷多次 “演化”,使其複雜化並更加逼真,與原始上下文結合後,構成了最終的合成數據集。雖然可選,你也可以選擇爲每個合成查詢 - 上下文對生成目標標籤,這將作爲給定查詢下你 LLM 系統的預期輸出。

一種數據合成器架構

在爲評估生成合成數據集時,主要有兩種方法:使用模型輸出進行自我提升,或者從更高級模型進行蒸餾。

自我提升方法,如 Self-Instruct 或 SPIN,受到模型能力的限制,可能會放大偏差和錯誤。相比之下,蒸餾技術僅受限於可用的最佳模型,確保了最高質量的生成。

數據優勝劣汰

讓我們澄清一下數據演進是什麼,以及爲什麼它對使用 LLM 的合成數據生成如此重要。數據演進,最初在 Microsoft 的 Evol-Instruct 中引入,涉及通過提示工程迭代增強現有的一組查詢,生成更復雜和多樣的查詢。這一步驟對於確保數據集的質量、全面性、複雜性和多樣性至關重要。正是它使得合成數據優於公共數據集或人工標註的數據集。

實際上,最初的作者僅從 175 個人工創建的查詢中就成功產生了 250,000 條指令。有三種類型的數據演進:

‘1+1’查詢的深度(藍色)和廣度(紅色)演進

進行深度演進有幾種方法,比如複雜化輸入、增加推理需求,或者向完成任務添加多個步驟。每種方法都對生成數據的複雜程度做出了貢獻。

深度演進確保創建出細緻、高質量的查詢,而廣度演進增強了多樣性和全面性。通過對每個查詢或指令進行多次演進,我們增加了其複雜性,從而產生了一個豐富且多維的數據集。但說這麼多,不如讓我展示如何將所有內容付諸實踐。

以這個查詢爲例:

1+1 等於多少?

我們可以將其深度演進爲:

在什麼情況下 1+1 不等於 2?

我希望我們都能同意,這比一般的 1+1 問題更復雜、更現實。在下一節中,我們將展示在生成合成數據集時如何實際運用這些演進方法。

分步指南:使用 LLM 生成合成數據

在開始之前,讓我們回顧一下我們將要構建的數據合成器架構:

一種數據合成器架構

你會發現主要有五個步驟:

  1. 文檔切塊

  2. 上下文生成

  3. 查詢生成

  4. 數據演進

  5. 標籤 / 預期輸出生成(可選)

當然,DeepEval 已經有一個功能完備的數據合成器,準備好用於合成數據集的生成(稍後在最後一節我會展示給你),但對於那些好奇具體操作的人,讓我們開始吧。

1. 文檔切塊

第一步是對文檔進行切塊。顧名思義,文檔切塊意味着將其分割成更小、更有意義的 “塊”。這樣,你可以將大文檔分解成易於管理的子文檔,同時保持其上下文。切塊還允許在超過嵌入模型令牌限制的文檔中生成嵌入。

這一步驟至關重要,因爲它有助於識別語義相似的塊,並基於共享上下文生成查詢或任務

有幾種切塊策略,如固定大小切塊和基於上下文的切塊。你還可以調整超參數,如字符大小和塊重疊。在下面的例子中,我們將使用基於令牌的切塊,字符大小爲 1024,無重疊。以下是切塊文檔的方法:

請注意,這裏的說明是通用指導,具體實現會根據使用的編程環境和技術棧有所不同。在實際操作中,你可能需要使用特定的庫或工具來完成文檔切塊,例如 Python 中的spacynltk或專門的文檔處理庫。以下是一個使用 Python 和假設的文檔切塊函數的示例:

pip install langchain langchain_openai
# Step 1. Chunk Documents
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import TokenTextSplitter

text_splitter = TokenTextSplitter(chunk_size=1024, chunk_overlap=0)
loader = PyPDFLoader("chatbot_information.pdf")
raw_chunks = loader.load_and_split(text_splitter)

一旦你有了這些分塊,就需要將每一個塊轉換成嵌入 (embeddings)。這些嵌入捕獲了每個塊的語義含義,並與塊的內容相結合,形成一個 Chunk 對象的列表。

from langchain_openai import OpenAIEmbeddings
...

embedding_model = OpenAIEmbeddings(api_key="...")
content = [rc.page_content for rc in raw_chunks]
embeddings = embedding_model.embed_documents(content)

2. 上下文生成

爲了生成上下文,首先隨機選擇一段數據作爲尋找相關信息的焦點錨點。

# Step 2: Generate context by selecting chunks
import random
...

reference_index = random.randint(0, len(embeddings) - 1)
reference_embedding = embeddings[reference_index]
contexts = [content[reference_index]]

下面,設置一個相似性閾值並使用餘弦相似度來識別出相關的塊來構建上下文:

...

similarity_threshold = 0.8
similar_indices = []
for i, embedding in enumerate(embeddings):
    product = np.dot(reference_embedding, embedding)
    norm = np.linalg.norm(reference_embedding) * np.linalg.norm(embedding)
    similarity = product / norm
    if similarity >= similarity_threshold:
        similar_indices.append(i)

for i in similar_indices:
    contexts.append(content[i])

這一步驟至關重要,因爲它允許你通過多樣化同一主題的信息來源來增強查詢的穩健性。通過包含多個具有相似主題的數據塊,你也爲模型提供了關於該主題的更豐富、更細膩的信息

這確保了你的查詢全面覆蓋主題,從而產生更全面、更準確的響應。

3. 查詢生成

現在到了使用 LLM 的有趣部分。使用 GPT 模型,通過結構化提示爲創建的上下文生成一系列任務或查詢。

提供一個提示,要求模型扮演文案撰寫者的角色,生成包含input鍵的 JSON 對象,這個鍵就是查詢。每個輸入應該要麼是一個可以用提供的上下文回答的問題,要麼是一個陳述句。

# Step 3. Generate a series of queries for similar chunks
from langchain_openai import ChatOpenAI
...

prompt = f"""I want you act as a copywriter. Based on the given context, 
which is list of strings, please generate a list of JSON objects 
with a `input` key. The `input` can either be a question or a 
statement that can be addressed by the given context.

contexts:
{contexts}"""

query = ChatOpenAI(openai_api_key="...").invoke(prompt)

這一步驟構成了你的查詢基礎,這些查詢將經過演進而被包含在最終的數據集中。

4. 查詢演進

最後,我們將使用多種演進模板來演進第三步中生成的查詢。你可以定義儘可能多的模板,但我們將會專注於三個:多上下文理解、多步驟推理和假設情景

# Evolution prompt templates as strings
multi_context_template = f"""
I want you to rewrite the given `input` so that it requires readers to use information from all elements in `Context`.

1. `Input` should require information from all `Context` elements. 
2. `Rewritten Input` must be concise and fully answerable from `Context`. 
3. Do not use phrases like 'based on the provided context.'
4. `Rewritten Input` should not exceed 15 words.

Context: {context}
Input: {original_input}
Rewritten Input:
"""

reasoning_template = f"""
I want you to rewrite the given `input` so that it explicitly requests multi-step reasoning.

1. `Rewritten Input` should require multiple logical connections or inferences.
2. `Rewritten Input` should be concise and understandable.
3. Do not use phrases like 'based on the provided context.'
4. `Rewritten Input` must be fully answerable from `Context`.
5. `Rewritten Input` should not exceed 15 words.

Context: {context}
Input: {original_input}
Rewritten Input:
"""

hypothetical_scenario_template = f"""
I want you to rewrite the given `input` to incorporate a hypothetical or speculative scenario.

1. `Rewritten Input` should encourage applying knowledge from `Context` to deduce outcomes.
2. `Rewritten Input` should be concise and understandable.
3. Do not use phrases like 'based on the provided context.'
4. `Rewritten Input` must be fully answerable from `Context`.
5. `Rewritten Input` should not exceed 15 words.

Context: {context}
Input: {original_input}
Rewritten Input:
"""

正如你所見,每個模板對輸出施加了特定的約束。你可以根據希望評估查詢在最終數據集中呈現的方式自由調整這些模板。我們將使用這些模板多次演進原始查詢,每次隨機選擇模板。

# Step 4. Evolve Queries
...

example_generated_query = "How do chatbots use natural language understanding?"
context = contexts 
original_input = example_generated_query 
evolution_templates = [multi_context_template, reasoning_template, hypothetical_scenario_template]

# Number of evolution steps to apply
num_evolution_steps = 3

# Function to perform random evolution steps
def evolve_query(original_input, context, steps):
    current_input = original_input
    for _ in range(steps):
        # Choose a random (or using custom logic) template from the list
        chosen_template = random.choice(evolution_templates)
        # Replace the placeholders with the current context and input
        evolved_prompt = chosen_template.replace("{context}", str(context)).replace("{original_input}", current_input)
        # Update the current input with the "Rewritten Input" section
        current_input = ChatOpenAI(openai_api_key="...").invoke(evolved_prompt)
    return current_input

# Evolve the input by randomly selecting the evolution type
evolved_query = evolve_query(original_input, context, num_evolution_steps)

就這樣,我們得到了最終的演進查詢!重複這一過程以生成更多查詢並進一步完善你的數據集。爲了評估目的,你需要將這些輸入查詢和上下文適當地格式化,融入一個合適的測試框架中。

5. 預期輸出生成

儘管這一步驟是可選的,但我強烈建議爲每個演進查詢生成預期輸出。這是因爲對於人類評估者來說,校正和標註預期輸出比從零開始創建它們更容易。

# Step 5. Generate Expected Output
...

# Define prompt template
expected_output_template = f"""
I want you to generate an answer for the given `input`. This answer has to be factually aligned to the provided context.

Context: {context}
Input: {evolved_query}
Answer:
"""

# Fill in the values
prompt = expected_output_template.replace("{context}", str(context)).replace("{evolved_query}", evolved_query)

# Generate expected output
expected_output = ChatOpenAI(openai_api_key="...").invoke(prompt)

作爲最後一步,將演進後的查詢、上下文和預期輸出組合成你合成數據集中的一行數據。

from pydantic import BaseModel
from typing import Optional, List
...

class SyntheticData(BaseModel):
 query: str
 expected_output: Optional[str]
 context: List[str]

synthetic_data = SyntheticData(
 query=evolved_query, 
 expected_output=expected_output, 
 context=context
)

# Simple implementation of synthetic dataset
synthetic_dataset = []
synthetic_dataset.append(synthetic_data)

現在,你所需要做的就是重複步驟 1 至 5,直到你擁有了一個足夠大的合成數據集,之後你可以使用它來評估和測試你的 LLM(系統)!

使用 DeepEval 生成合成數據集

在本節的最後,我想向你展示一個經過實戰考驗的數據合成器,我已經在 DeepEval 中開源了它。這包括從合成數據生成到將其格式化爲準備好的測試案例,用於 LLM 評估和測試,你只需兩行代碼就能做到這一切。最棒的是,你可以利用任何你選擇的 LLM。以下是如何使用 DeepEval 進行合成數據集生成的方法:

pip install deepeval
from deepeval.synthesizer import Synthesizer

synthesizer = Synthesizer()
synthesizer.generate_goldens_from_docs(
    document_paths=['example.txt''example.docx''example.pdf'],
    max_goldens_per_document=2
)

你可以在 DeepEval 的文檔中閱讀更多關於如何使用 DeepEval 的合成器來生成合成數據集的詳細信息,但總的來說,DeepEval 接收你的文檔,爲你完成所有的切塊和上下文生成工作,然後生成合成的 “金標準” 數據行,這些數據行最終構成你的合成數據集。是不是很簡單?

結論

使用 LLM 生成合成數據集很棒,因爲這是一種快速且成本低廉的方式來獲取大量數據。然而,生成的數據可能看起來非常重複,很多時候無法充分代表底層數據分佈,以至於被認爲不夠有用。在本文中,我們討論瞭如何通過首先從文檔中選擇相關上下文,然後利用它來生成可用於測試和評估你的 LLM 系統的查詢,來解決這個問題。

我們還探討了數據演進,我們用它來使合成查詢更加逼真。如果你正在從零開始構建數據合成器,這篇文章將是一個很好的教程。然而,如果你正在尋找更強大、更適用於生產環境的解決方案,你可以使用 DeepEval。它是開源的,極其容易使用(真的),並且有一整套評估和測試工具,你可以使用生成的合成數據集無縫地測試和評估你的 LLM 系統。

英文原文:https://www.confident-ai.com/blog/the-definitive-guide-to-synthetic-data-generation-using-llms

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/jT7AduuZHqjG1wENlwgQJg