利用LLM生成人工合成資料

LexLuc發表於2024-08-19
  • 編寫prompt讓LLM直接生成結構化的資料
  • 用prompt讓LLM生成能夠生成結構化資料的程式碼
  • 用prompt讓LLM合成文字資料
  • 處理不平衡(imbalanced)或非多樣化(non-diverse)的文字資料

合成資料(Synthetic Data)是指透過人工生成的方式建立的資料,而非從現實世界直接收集的資料。通常,合成資料用於替代或補充真實資料,尤其是在真實資料難以獲取、昂貴、涉及隱私問題或數量不足的情況下。合成資料在人工智慧、機器學習、計算機視覺、資料隱私保護等多個領域中得到了廣泛應用。

編寫prompt讓LLM直接生成結構化的資料

例1. 讓GPT-4o-mini生成CSV格式的住宅資料表:

建立一個包含10行住房資料的CSV檔案。
每一行應包括以下欄位:
 - id(從1開始的遞增整數)
 - 房屋面積(平方米)
 - 房屋價格
 - 地點
 - 臥室數量

請確保數字合理(例如,更多的房間通常意味著更大的面積,更貴的地點會提高價格,面積越大通常價格越高等。請確保所有的數字都是合理的)。另外,只需以CSV格式回覆。

用prompt讓LLM生成能夠生成結構化資料的程式碼

由於成本以及上下文視窗大小限制等原因,讓LLM寫程式碼生成資料將會是更加高效並且可控的方式。
例1. 讓GPT-4o-mini生成CSV格式的住宅資料表:

請建立一個 Python 程式,用於生成100行房屋資料。最終輸出應為包含100行資料的 pandas 資料框架。每一行資料應包括以下欄位:
 - id(從1開始遞增的整數)
 - 房屋面積(平方米)
 - 房屋價格
 - 位置
 - 臥室數量

請確保生成的數字合理(例如,更多的房間通常意味著更大的面積,更昂貴的位置會提高價格,更大的面積通常意味著更高的價格等)。確保所有數字都合理。

例2. 資料集往往會更復雜,比如住宅資料可能會存在多張表:住房、地址、房屋型別,並且彼此有相互關聯,因此需要在prompt中描述清楚表之間的關係,表資料量彼此匹配,以及主外來鍵關係正確等:

請建立一個Python程式來生成三個不同的Pandas Dataframes。

1. **住房資料**
   - 我需要100行資料。每一行應包含以下欄位:
     - id(從1開始遞增的整數)
     - 房屋面積(m²)
     - 房屋價格
     - 位置
     - 臥室數量
     - 房屋型別
     - 任何相關的外來鍵

2. **位置**
   - 每一行應包含以下欄位:
     - id(從1開始遞增的整數)
     - 國家
     - 城市
     - 人口
     - 面積(m²)
     - 任何相關的外來鍵

3. **房屋型別**
   - id(從1開始遞增的整數)
   - 房屋型別
   - 房屋型別的平均價格
   - 房屋數量
   - 任何相關的外來鍵

請確保生成的資料符合邏輯(例如:更多的房間通常意味著更大的面積,價格更高的地點通常房價更高,面積越大價格通常越高等)。
請確保Dataframes之間的關係符合常識性的檢查,例如:Dataframes的大小在彼此比較時合理。
請確保外來鍵匹配,並且在建立每個Dataframe時可以使用之前生成的Dataframes。
你可以使用之前生成的Dataframe來生成下一個Dataframe。

用prompt讓LLM合成文字資料

合成文字資料通常可以用於訓練或微調語言模型。
例1. 一個零售商需要訓練語言模型生成商品的描述。定義好輸入輸出和一定的資料格式:

我正在建立輸入輸出訓練對,以微調我的GPT模型。使用場景是零售商根據產品目錄生成產品描述。我希望輸入為產品名稱和類別(產品所屬類別),輸出為產品描述。

格式應為以下形式:
1.
輸入:產品名稱,類別
輸出:描述
2.
輸入:產品名稱,類別
輸出:描述

請勿在此格式周圍新增任何多餘字元,否則將導致輸出解析出錯。
請儘可能多地建立訓練對。

處理不平衡(imbalanced)或非多樣化(non-diverse)的文字資料

高質量的合成文字資料應該滿足幾個條件:

  1. 準確性:資料是否符合事實;
  2. 一致性:相同的輸入對應的輸出是否(基本)相同;
  3. 多樣性:資料點是否儘可能多地覆蓋真實場景的整個分佈;
  4. 平衡性:每種類別的資料數量是否相當;
    聚類演算法可以幫助我們發現資料中的不平衡和非多樣化的問題:
  • cluster的資料點數量差距大:不平衡
  • 某些cluster中沒有資料:非多樣化
    我們可以使用遞迴的方式執行生成+聚類分析過程,實現自動生成高質量的合成文字資料。
    例1. 一個零售商需要訓練語言模型生成商品的描述。定義好商品主題、輸入輸出和一定的資料格式:
我正在建立輸入輸出訓練對,以微調我的GPT模型。我希望輸入為產品名稱和類別,輸出為描述。類別應包括以下內容:手機、鞋子、耳機、膝上型電腦、電動牙刷等。更重要的是,這些類別應歸納為四個主要主題:交通工具、服裝、洗漱用品、食品。

在每個示例的數量後,還應標明主題區域。格式應如下所示:
1. 主題區域
   輸入:產品名稱,類別
   輸出:描述

請勿在格式周圍新增任何額外字元,以免導致輸出解析錯誤。

以下是一些有用的示例,以幫助您正確理解輸出樣式。

1) 服裝
   輸入:“鞋子名稱,鞋子”
   輸出:“體驗無與倫比的舒適感。這些鞋子融合了現代風格和傳統的優越緩衝,完美適合那些總是忙碌的人。”
 
輸出樣例:
1. 交通工具
輸入: "特斯拉 Model 3, 電動汽車"  
輸出: "特斯拉 Model 3 是一款革命性的電動汽車,擁有令人印象深刻的續航能力和尖端技術,旨在提供令人振奮的駕駛體驗,同時最大程度地減少對環境的影響。"

2. 服裝
輸入: "耐克 Air Max, 鞋子"  
輸出: "提升您的運動鞋風格,選擇耐克 Air Max。這款鞋子將標誌性的風格與卓越的舒適性和支撐性相結合,適合鍛鍊和休閒場合。"

3. 日用品
輸入: "Oral-B Pro 1000, 電動牙刷"  
輸出: "使用 Oral-B Pro 1000 實現卓越的清潔效果。這款電子牙刷具有3D清潔功能,透過脈動和振動去除比普通手動牙刷更多的牙菌斑。"

4. 食品
輸入: "Chobani 希臘酸奶, 酸奶"  
輸出: "享受營養豐富的零食,選擇 Chobani 希臘酸奶。富含蛋白質和美味的口味,是健康早餐或隨時享用的理想選擇。"

5. 交通工具

用Python正規表示式解析輸出,並提取產品名稱列:

pattern = re.compile(r'(\d+)\.\s*(\w+)\s*Input:\s*"(.+?),\s*(.+?)"\s*Output:\s*"(.*?)"', re.DOTALL)
pattern = re.compile(r'(\d+)\.\s*(\w+)\s*Input:\s*"(.+?),\s*(.+?)"\s*Output:\s*"(.*?)"', re.DOTALL)
matches = pattern.findall(output_string)

topics = []
products = []
categories = []
descriptions = []

for match in matches:
    number, topic, product, category, description = match
    topics.append(topic)
    products.append(product)

首先我們對文字進行向量化。cluster的個數可以用k-means + elbow演算法估算。

def get_embedding(text, model="text-embedding-3-small"):
    text = text.replace("\n", " ")

    response = client.embeddings.create(input=[text], model=model)

    return response.data[0].embedding

embedding_model = "text-embedding-3-small"
df["embedding"] = df.Category.apply(lambda x: get_embedding(x, model=embedding_model))

if len(df.embedding.values) > 0:
    matrix = np.vstack(df.embedding.values)
else:
    matrix = np.array([])
inertias = []
range_of_clusters = range(1, 13)  # 嘗試的cluster數量範圍

for n_clusters in range_of_clusters:
    kmeans = KMeans(n_clusters=n_clusters, init="k-means++", random_state=42, n_init=10)
    kmeans.fit(matrix)
    inertias.append(kmeans.inertia_)

估算後得到最優的cluster數量可以是3、4、5,這裡我們取5:

n_clusters = 5

kmeans = KMeans(n_clusters=n_clusters, init="k-means++", random_state=42)
kmeans.fit(matrix)
labels = kmeans.labels_
df["Cluster"] = labels

接下來需要分析聚類資料。首先看資料平衡性如何:

cluster_counts = df["Cluster"].value_counts().sort_index()
print(cluster_counts)

結果顯示

Cluster
0    5
1    7
2    8
3    6
4    2
Name: count, dtype: int64

可以提問LLM新的clusters屬於什麼主題名稱。

selected_examples = df.groupby('Cluster').apply(lambda x: x.sample(3, replace=True)).reset_index(drop=True)

# Format the selected examples
formatted_examples = "\n".join(
    f'Input: "{row["Product"]}, {row["Category"]}"\nOutput: "{row["Description"]}"\nCluster: "{row["Cluster"]}"'
    for _, row in selected_examples.iterrows()
)

topic_prompt = f"""
    I previously generated some examples of input output trainings pairs and then I clustered them based on category. From each cluster I picked 3 example data point which you can find below.
    I want you identify the broad topic areas these clusters belong to.
    Previous examples:
    {formatted_examples}


    Your output should be strictly of the format:
    Cluster: number, topic: topic
    Cluster: number, topic: topic
    Cluster: number, topic: topic

    Do not add any extra characters around that formatting as it will make the output parsing break.
    """

response = client.chat.completions.create(
  model=datagen_model,
  messages=[
    {"role": "system", "content": "You are a helpful assistant designed analyze clustered data"},
    {"role": "user", "content": topic_prompt}
  ]
)
res = response.choices[0].message.content

pattern = r"Cluster: (\d+), topic: ([^\n]+)"
matches = re.findall(pattern, res)
clusters = [{"cluster": int(cluster), "topic": topic} for cluster, topic in matches]
json_output = json.dumps(clusters, indent=2)
print(json_output)

可以針對性地讓LLM提供更多資料點比較少的cluster的資料以減少不平衡。
此外,為了增加資料多樣性,我們可以從每個cluster隨機抽取一些資料點並讓LLM生成更多的商品類別(程式碼複用了讓LLM給cluster起名):

selected_examples = df.groupby('Cluster').apply(lambda x: x.sample(3, replace=True)).reset_index(drop=True)

# Format the selected examples
formatted_examples = "\n".join(
    f'Input: "{row["Product"]}, {row["Category"]}"\nOutput: "{row["Description"]}"\nCluster: "{row["Cluster"]}"'
    for _, row in selected_examples.iterrows()
)

topic_prompt = f"""
    I previously generated some examples of input output trainings pairs and then I clustered them based on category. From each cluster I picked 3 example data point which you can find below.
    I want to promote diversity in my examples across categories so follow the procedure below:
    1. You must identify the broad topic areas these clusters belong to.
    2. You should generate further topic areas which don't exist so I can generate data within these topics to improve diversity.


    Previous examples:
    {formatted_examples}


    Your output should be strictly of the format:

    1. Cluster topic mapping
    Cluster: number, topic: topic
    Cluster: number, topic: topic
    Cluster: number, topic: topic

    2. New topics
    1. topic
    2. topic
    3. topic
    4. topic

    Do not add any extra characters around that formatting as it will make the output parsing break. It is very important you stick to that output format
    """

response = client.chat.completions.create(
  model=datagen_model,
  messages=[
    {"role": "system", "content": "You are a helpful assistant designed to analyze clustered data"},
    {"role": "user", "content": topic_prompt}
  ]
)
res = response.choices[0].message.content
print(res)

以下是新生成的商品類別

1. 汽車
2. 個人護理
3. 鞋類
4. 食品
5. 電動車

6. 家用電器
7. 戶外裝置
8. 智慧家居技術
9. 健身裝置

最後,讓LLM用這些新的商品類別擴充套件更多樣的資料:

我正在建立輸入輸出訓練對,以便微調我的GPT模型。我希望輸入為產品名稱和類別,輸出為描述。類別應包括如:手機、鞋子、耳機、膝上型電腦、電動牙刷等,並且更重要的是,這些類別應歸屬於一些主要主題:汽車, 個人護理, 鞋類, 食品, 電動車, 家用電器, 戶外裝置, 智慧家居技術, 健身裝置。

在每個示例的數量後,還需註明主題領域。格式應如下所示:
1. 主題領域
   輸入:產品名稱,類別
   輸出:描述

請不要在格式周圍新增任何額外字元,以免破壞輸出解析。

以下是一些有幫助的示例,以便您瞭解正確的輸出風格。

1) 服裝
   輸入:“鞋子名稱,鞋子”
   輸出:“體驗無與倫比的舒適。這些鞋子融合了現代風格和傳統的優質緩震,非常適合那些經常活動的人。”

參考openAI Cookbook,英文原文在這裡

相關文章