隨著 RAG 資料量的增長,儲存和檢索成本日益提升。為瞭解決這個問題,可以透過微調 OpenAI 模型來減少對大量靜態 RAG 資料的依賴。本文以 SciQ 硬科學資料集為例,示範如何將其轉換為 OpenAI 微調所需的 JSONL 格式,並用於訓練 GPT-4o-mini 模型。這個過程包含資料準備、模型微調、任務監控和結果驗證等步驟,旨在提升模型在特定領域的問答能力,並有效降低對龐大資料集的依賴。

微調OpenAI模型的RAG資料縮減架構

在本章中,我們將探討透過微調來實作RAG資料縮減的架構,並重點關注包含現成檔案和強調人為反饋因素的資料集。我們將展示如何將非引數資料轉換為引數化的微調資料,並在OpenAI模型中實作。

微調的架構

靜態RAG資料的挑戰

在本文中,我們將探討當非引數RAG資料超過可管理的閾值時的使用問題。如同在第一章《為什麼選擇檢索增強生成?》中所述,靜態資料的處理(D2)和儲存(D3)閾值已經達到。閾值的大小取決於每個專案和諸如以下引數:

  • 要處理的RAG資料量:嵌入資料需要人力和機器資源。即使我們不嵌入資料,堆積積靜態資料(長時間保持穩定的資料)也毫無意義。
  • 要儲存和檢索的RAG資料量:在某些時候,如果我們繼續堆積積資料,其中很多可能會重疊。
  • 檢索需要資源:即使系統是開源的,仍然需要管理越來越多的資源。

RAG生態系統

在本文中,我們將回到第一章中描述的RAG生態系統,並重點關注本章所需的特定元件。下圖展示了微調元件的彩色表示和我們不需要的元件的灰色表示:

此圖示呈現了我們將要構建的微調生態系統的關鍵特徵,主要包括:

  • 收集(D1)和準備(D2)資料集:我們將下載並處理在前一章中實作的人工製作的眾包SciQ硬科學資料集。
  • 人為反饋(E2):我們可以假設人為反饋在SciQ硬科學資料集中發揮了重要作用。該資料集由人類控制和更新,因此可以認為它是可靠的人為反饋模擬,可以透過微調來減輕RAG資料集的數量。
  • 微調(T2):我們將微調一個具有成本效益的OpenAI模型——GPT-4o-mini。
  • 提示工程(G3)和生成與輸出(G4):我們將按照OpenAI的建議設計提示並顯示輸出。
  • 指標(E1):我們將檢視OpenAI指標介面的主要功能。

安裝環境

安裝環境由於AI和跨平台依賴衝突的快速演變而變得複雜。因此,我們將盡可能凍結包版本。

程式碼實作

# 從檔案中檢索API金鑰或手動輸入
from google.colab import drive
drive.mount('/content/drive')
f = open("drive/MyDrive/files/api_key.txt", "r")
API_KEY = f.readline()
f.close()

try:
    import openai
except:
    !pip install openai==1.42.0
import openai

import os
os.environ['OPENAI_API_KEY'] = API_KEY
openai.api_key = os.getenv("OPENAI_API_KEY")

!pip install jsonlines==4.0.0
!pip install datasets==2.20.0

內容解密:

這段程式碼主要用於設定OpenAI API金鑰和安裝必要的Python包。首先,它從Google Drive掛載的檔案中讀取API金鑰,或者可以手動輸入。然後,它嘗試匯入openai函式庫,如果未安裝,則安裝指定版本(1.42.0)。接著,它設定環境變數OPENAI_API_KEY並將其指定給openai.api_key。最後,它安裝jsonlinesdatasets函式庫,分別用於處理JSONL資料和資料集操作。

準備微調資料集

微調OpenAI模型需要仔細準備,否則微調作業將失敗。在本文中,我們將執行以下步驟:

  1. 從Hugging Face下載資料集並透過處理其列來準備。
  2. 將資料集流式傳輸到JSONL格式的檔案中。

下載和視覺化資料集

# 下載SciQ資料集
from datasets import load_dataset
dataset = load_dataset("sciq")

內容解密:

這段程式碼使用datasets函式庫從Hugging Face下載SciQ資料集。下載後的資料集儲存在dataset變數中,可以用於後續的處理和微調。

圖表翻譯:

此圖示呈現了RAG生態系統中的微調元件及其相互關係。主要包括資料收集、準備、微調、提示工程和指標評估等關鍵步驟。

後續步驟

在下一節中,我們將繼續探討如何準備JSONL格式的資料集以進行微調,並使用OpenAI的處理工具生成JSONL資料集。然後,我們將微調GPT-4o-mini模型,並測試其在我們的資料集上的表現。最後,我們將探索OpenAI的指標介面,以評估我們的技術指標,如準確性和使用指標,以評估我們的成本效益方法。

微調GPT-4o-mini模型以增強科學問答能力

在人工智慧領域,模型的微調(fine-tuning)是提升特定任務表現的重要步驟。本章節將介紹如何下載並視覺化Hugging Face資料集,接著將資料集轉換為適合OpenAI微調的格式,最後進行模型的微調和監控。

1.1. 下載和視覺化資料集

首先,我們下載Hugging Face上的SciQ資料集,並過濾出包含支援文字的訓練資料:

# 匯入必要的函式庫
from datasets import load_dataset
import pandas as pd

# 從HuggingFace載入SciQ資料集
dataset_view = load_dataset("sciq", split="train")

# 過濾資料集,只保留包含支援文字的題目
filtered_dataset = dataset_view.filter(lambda x: x["support"] != "")

# 列印過濾後題目的數量
print("包含支援文字的題目數量:", len(filtered_dataset))

內容解密:

  1. load_dataset("sciq", split="train"):從Hugging Face載入SciQ資料集的訓練部分。
  2. dataset_view.filter(lambda x: x["support"] != ""):使用lambda函式過濾資料,只保留support欄位不為空的記錄。
  3. len(filtered_dataset):計算過濾後的資料集大小。

輸出結果顯示,我們得到了10,481筆包含支援文字的題目。

接著,將過濾後的資料轉換為pandas DataFrame,並移除包含錯誤答案的欄位:

# 將過濾後的資料集轉換為pandas DataFrame
df_view = pd.DataFrame(filtered_dataset)

# 需要移除的欄位列表
columns_to_drop = ['distractor3', 'distractor1', 'distractor2']

# 從DataFrame中移除指定的欄位
df_view = df_view.drop(columns=columns_to_drop)

# 顯示DataFrame的前幾行
df_view.head()

內容解密:

  1. pd.DataFrame(filtered_dataset):將Hugging Face的Dataset物件轉換為pandas DataFrame。
  2. df_view.drop(columns=columns_to_drop):移除DataFrame中指定的欄位,這些欄位包含錯誤的答案選項。

1.2. 為微調準備資料集

為了微調GPT-4o-mini模型,我們需要將資料集轉換為JSONL格式。以下程式碼展示瞭如何準備資料:

# 準備JSONL檔案的資料專案
items = []
for idx, row in df.iterrows():
    detailed_answer = row['correct_answer'] + " 解釋: " + row['support']
    items.append({
        "messages": [
            {"role": "system", "content": "針對科學問題提供詳細答案"},
            {"role": "user", "content": row['question']},
            {"role": "assistant", "content": detailed_answer}
        ]
    })

# 將資料寫入JSONL檔案
import jsonlines
with jsonlines.open('/content/QA_prompts_and_completions.json', mode='w') as writer:
    writer.write_all(items)

內容解密:

  1. detailed_answer = row['correct_answer'] + " 解釋: " + row['support']:結合正確答案和支援文字,形成詳細的答案。
  2. items.append({...}):將每個問題和對應的詳細答案組織成符合OpenAI格式的字典。
  3. jsonlines.open(...).write_all(items):將準備好的資料寫入JSONL檔案。

2. 微調模型

2.1. 建立微調任務

首先,建立OpenAI客戶端並上傳訓練檔案:

from openai import OpenAI
import jsonlines

# 建立OpenAI客戶端
client = OpenAI()

# 上傳訓練檔案
result_file = client.files.create(
    file=open("QA_prompts_and_completions.json", "rb"),
    purpose="fine-tune"
)

# 列印檔案資訊
print(result_file)
param_training_file_name = result_file.id
print(param_training_file_name)

內容解密:

  1. client.files.create(file=open(...), purpose="fine-tune"):上傳JSONL檔案作為微調任務的訓練資料。
  2. result_file.id:取得上傳檔案的ID,用於後續的微調任務。

接著,建立並顯示微調任務:

# 建立微調任務
ft_job = client.fine_tuning.jobs.create(
    training_file=param_training_file_name,
    model="gpt-4o-mini-2024-07-18"
)

# 列印微調任務資訊
print(ft_job)

內容解密:

  1. client.fine_tuning.jobs.create(training_file=..., model=...):使用指定的訓練檔案和模型建立微調任務。
  2. print(ft_job):輸出微調任務的詳細資訊,包括任務ID、狀態、模型名稱等。

2.2. 監控微調任務

為了監控微調任務的進度,可以查詢最近的三個微調任務:

# 取得最近的三個微調任務
response = client.fine_tuning.jobs.list(limit=3)

# 列印任務資訊
for job in response.data:
    print(job.id, job.status, job.created_at)

內容解密:

  1. client.fine_tuning.jobs.list(limit=3):取得最近建立的三個微調任務。
  2. for job in response.data::遍歷並列印每個任務的ID、狀態和建立時間。

此圖表呈現了微調任務的主要流程:

  graph LR;
    A[下載並視覺化資料集] --> B[準備JSONL格式資料];
    B --> C[上傳訓練檔案];
    C --> D[建立微調任務];
    D --> E[監控微調任務進度];

圖表翻譯: 此圖示展示了從下載資料集到監控微調任務的完整流程。首先,下載並視覺化SciQ資料集。接著,將資料轉換為JSONL格式並上傳至OpenAI。然後,使用上傳的檔案建立微調任務,並持續監控任務的進度和狀態。整個流程確保模型的微調順利進行並達到預期的效果。

微調OpenAI模型與應用實踐

在人工智慧與機器學習的領域中,模型的微調(Fine-Tuning)是提升模型效能的重要步驟。本文將介紹如何使用OpenAI的API進行模型的微調,並展示相關的程式碼實作與結果分析。

取得微調任務資訊

首先,我們需要取得OpenAI中已建立的微調任務資訊。透過client.fine_tuning.jobs.list方法,可以列出最近的微調任務。

import pandas as pd
from openai import OpenAI

client = OpenAI()
response = client.fine_tuning.jobs.list(limit=3)

# 初始化列表以儲存提取的資料
job_ids = []
created_ats = []
statuses = []
models = []
training_files = []
error_messages = []
fine_tuned_models = []

# 遍歷任務以檢索所需資訊
for job in response.data:
    job_ids.append(job.id)
    created_ats.append(job.created_at)
    statuses.append(job.status)
    models.append(job.model)
    training_files.append(job.training_file)
    error_message = job.error.message if job.error else None
    error_messages.append(error_message)
    fine_tuned_model = job.fine_tuned_model if hasattr(job, 'fine_tuned_model') else None
    fine_tuned_models.append(fine_tuned_model)

# 建立DataFrame
df = pd.DataFrame({
    'Job ID': job_ids,
    'Created At': created_ats,
    'Status': statuses,
    'Model': models,
    'Training File': training_files,
    'Error Message': error_messages,
    'Fine-Tuned Model': fine_tuned_models
})

# 將時間戳轉換為可讀格式並排序
df['Created At'] = pd.to_datetime(df['Created At'], unit='s')
df = df.sort_values(by='Created At', ascending=False)

# 顯示DataFrame
print(df)

內容解密:

  1. client.fine_tuning.jobs.list(limit=3):列出最近的三個微調任務。
  2. for job in response.data:遍歷任務列表,提取每個任務的詳細資訊,包括任務ID、建立時間、狀態、模型、訓練檔案、錯誤訊息和微調後的模型名稱。
  3. pd.DataFrame:將提取的資訊儲存到DataFrame中,便於後續分析和處理。
  4. pd.to_datetime(df['Created At'], unit='s'):將Unix時間戳轉換為可讀的日期時間格式。
  5. df.sort_values(by='Created At', ascending=False):按建立時間對DataFrame進行降序排序。

檢查最新的微調模型

接下來,我們需要檢查最新的微調模型是否已經完成訓練。

generation = False  # 初始狀態為False,表示未完成訓練

# 尋找第一個非空的Fine-Tuned Model
non_empty_models = df[df['Fine-Tuned Model'].notna()]

if not non_empty_models.empty:
    first_non_empty_model = non_empty_models.iloc[0]['Fine-Tuned Model']
    print("The latest fine-tuned model is:", first_non_empty_model)
    generation = True
else:
    print("No fine-tuned models found.")

內容解密:

  1. non_empty_models = df[df['Fine-Tuned Model'].notna()]:篩選出Fine-Tuned Model欄位非空的行。
  2. generation = True:如果找到非空的微調模型,將generation設為True,表示可以進行後續的操作。

使用微調後的OpenAI模型

當微調模型訓練完成後,我們可以使用它來進行推論。

if generation:
    prompt = "What phenomenon makes global winds blow northeast to southwest?"
    response = client.chat.completions.create(
        model=first_non_empty_model,
        temperature=0.0,
        messages=[
            {"role": "system", "content": "Given a question, respond with the appropriate answer."},
            {"role": "user", "content": prompt}
        ]
    )
    print(response.choices[0].message.content)
else:
    print("Error: Model is None, cannot proceed with the API request.")

內容解密:

  1. client.chat.completions.create:使用微調後的模型進行推論。
  2. temperature=0.0:設定溫度為0,表示不引入隨機性,輸出結果更具確定性。
  3. messages:定義對話內容,包括系統提示和使用者輸入的問題。

結果格式化

最後,我們可以對模型的輸出結果進行格式化,使其更易閱讀。

import textwrap

if generation:
    response_text = response.choices[0].message.content
    wrapped_text = textwrap.fill(response_text.strip(), 60)
    print(wrapped_text)

內容解密:

  1. textwrap.fill:將長字串按照指定的寬度進行換行處理,使輸出結果更整潔。