2024-07-18 | Teodora Musatoiu
歡迎來到服裝搭配應用 Jupyter Notebook!本專案展示了 GPT-4o mini 模型在分析服裝圖片並提取關鍵特徵(如顏色、風格和類型)方面的強大功能。我們的應用核心依賴於這款由 OpenAI 開發的先進圖像分析模型,使我們能夠準確識別輸入服裝的特徵。
GPT-4o mini 是一款結合自然語言處理與圖像識別的小型模型,能夠基於文字和視覺輸入來理解和生成回應,且具備低延遲的特點。
基於 GPT-4o mini 模型的能力,我們使用了一個自訂的匹配算法以及 RAG 技術來搜尋知識庫中與識別特徵相符的項目。該算法考慮了顏色搭配與風格一致性等因素,為用戶提供合適的推薦。透過這個 Notebook,我們希望展示這些技術在打造服裝推薦系統中的實際應用。
結合 GPT-4o mini 與 RAG(檢索增強生成)的優勢有以下幾點:
- 上下文理解:GPT-4o mini 可以分析輸入影像並理解上下文,例如所描繪的物件、場景和活動。這樣可以在各個領域(無論是室內設計、烹飪或教育)提供更準確、更相關的建議或資訊。
- 豐富的知識庫:RAG 將 GPT-4 的生成功能與可存取跨不同領域的大量資訊的檢索元件結合。這意味著該系統可以根據從歷史事實到科學概念的廣泛知識提供建議或見解。
- 定制:該方法允許輕鬆定制,以滿足各種應用程式中的特定用戶需求或偏好。無論是根據使用者的藝術品味客製化建議,還是根據學生的學習程度提供教育內容,系統都可以適應提供個人化的體驗。
總體而言,GPT-4o mini + RAG 方法利用基於生成和檢索的人工智慧技術的優勢,為各種時尚相關應用提供了快速、強大且靈活的解決方案。
環境設定
首先,我們將安裝必要的套件,然後導入相關的函式庫,並撰寫一些之後會用到的工具函式。
%pip install openai --quiet
%pip install tenacity --quiet
%pip install tqdm --quiet
%pip install numpy --quiet
%pip install typing --quiet
%pip install tiktoken --quiet
%pip install concurrent --quiet
import pandas as pd
import numpy as np
import json
import ast
import tiktoken
import concurrent
from openai import OpenAI
from tqdm import tqdm
from tenacity import retry, wait_random_exponential, stop_after_attempt
from IPython.display import Image, display, HTML
from typing import List
client = OpenAI()
GPT_MODEL = "gpt-4o-mini"
EMBEDDING_MODEL = "text-embedding-3-large"
EMBEDDING_COST_PER_1K_TOKENS = 0.00013
建立嵌入
我們現在將透過選擇資料庫並為其產生嵌入來建立知識庫。我正在sample_styles.csv
資料資料夾中使用該文件。這是包含專案的更大資料集的範例~44K
。此步驟也可以透過使用現成的向量資料庫來取代。例如,您可以按照其中一本食譜來設定向量資料庫。
styles_filepath = "data/sample_clothes/sample_styles.csv"
styles_df = pd.read_csv(styles_filepath, on_bad_lines='skip')
print(styles_df.head())
print("Opened dataset successfully. Dataset has {} items of clothing.".format(len(styles_df)))
現在我們將為整個資料集產生嵌入。我們可以並行執行這些嵌入,以確保腳本可以擴展到更大的資料集。透過這種邏輯,為完整條目資料集建立嵌入的時間44K
從約 4 小時減少到約 2-3 分鐘。
## Batch Embedding Logic
# Simple function to take in a list of text objects and return them as a list of embeddings
@retry(wait=wait_random_exponential(min=1, max=40), stop=stop_after_attempt(10))
def get_embeddings(input: List):
response = client.embeddings.create(
input=input,
model=EMBEDDING_MODEL
).data
return [data.embedding for data in response]
# Splits an iterable into batches of size n.
def batchify(iterable, n=1):
l = len(iterable)
for ndx in range(0, l, n):
yield iterable[ndx : min(ndx + n, l)]
# Function for batching and parallel processing the embeddings
def embed_corpus(
corpus: List[str],
batch_size=64,
num_workers=8,
max_context_len=8191,
):
# Encode the corpus, truncating to max_context_len
encoding = tiktoken.get_encoding("cl100k_base")
encoded_corpus = [
encoded_article[:max_context_len] for encoded_article in encoding.encode_batch(corpus)
]
# Calculate corpus statistics: the number of inputs, the total number of tokens, and the estimated cost to embed
num_tokens = sum(len(article) for article in encoded_corpus)
cost_to_embed_tokens = num_tokens / 1000 * EMBEDDING_COST_PER_1K_TOKENS
print(
f"num_articles={len(encoded_corpus)}, num_tokens={num_tokens}, est_embedding_cost={cost_to_embed_tokens:.2f} USD"
)
# Embed the corpus
with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor:
futures = [
executor.submit(get_embeddings, text_batch)
for text_batch in batchify(encoded_corpus, batch_size)
]
with tqdm(total=len(encoded_corpus)) as pbar:
for _ in concurrent.futures.as_completed(futures):
pbar.update(batch_size)
embeddings = []
for future in futures:
data = future.result()
embeddings.extend(data)
return embeddings
# Function to generate embeddings for a given column in a DataFrame
def generate_embeddings(df, column_name):
# Initialize an empty list to store embeddings
descriptions = df[column_name].astype(str).tolist()
embeddings = embed_corpus(descriptions)
# Add the embeddings as a new column to the DataFrame
df['embeddings'] = embeddings
print("Embeddings created successfully.")
建立嵌入的兩個選項:
下一行程式碼將為樣本服裝數據集創建嵌入。這個過程大約需要 0.02 秒處理時間,並且需要約 30 秒將結果寫入本地 .csv 文件。該過程使用的是我們的 text_embedding_3_large
模型,每 1,000 個 token 收費 $0.00013。由於數據集約有 1,000 條記錄,因此這個操作的成本約為 $0.001。如果選擇使用完整的 44,000 條記錄數據集,這個操作將需要 2-3 分鐘處理時間,成本約為 $0.07。
如果您不想自行創建嵌入,我們將使用預先計算的嵌入數據集。您可以跳過此單元,並取消註解下一單元中的程式碼以載入預先計算的向量。此操作大約需要 1 分鐘將所有數據載入到記憶體中。
generate_embeddings(styles_df, 'productDisplayName')
print("Writing embeddings to file ...")
styles_df.to_csv('data/sample_clothes/sample_styles_with_embeddings.csv', index=False)
print("Embeddings successfully stored in sample_styles_with_embeddings.csv")
# styles_df = pd.read_csv('data/sample_clothes/sample_styles_with_embeddings.csv', on_bad_lines='skip')
# # Convert the 'embeddings' column from string representations of lists to actual lists of floats
# styles_df['embeddings'] = styles_df['embeddings'].apply(lambda x: ast.literal_eval(x))
print(styles_df.head())
print("Opened dataset successfully. Dataset has {} items of clothing along with their embeddings.".format(len(styles_df)))
建構匹配演算法
在本節中,我們將開發一個餘弦相似度檢索算法,用於在數據框中查找相似的項目。我們將使用自訂的餘弦相似度函式來達成此目的。雖然 sklearn 函式庫提供內建的餘弦相似度函式,但由於最近的 SDK 更新導致相容性問題,因此我們決定實作自己的標準餘弦相似度計算。
如果您已經設置了向量資料庫,可以跳過這個步驟。大多數標準資料庫都附帶自己的搜尋功能,可以簡化本指南後續的操作。不過,我們希望展示如何根據特定需求來定制匹配算法,例如設定特定的門檻值或返回指定數量的匹配結果。
find_similar_items
函式接受四個參數:
embedding
:我們想要找到匹配項的嵌入。embeddings
:用於搜尋最佳匹配的嵌入清單。threshold
(可選):此參數指定匹配被視為有效的最小相似度分數。較高的閾值會導致更接近(更好)的匹配,而較低的閾值允許返回更多項目,儘管它們可能與初始 不那麼緊密匹配embedding
。top_k
(可選):此參數確定返回超過給定閾值的項目數。這些將是所提供的得分最高的比賽embedding
。
def cosine_similarity_manual(vec1, vec2):
"""Calculate the cosine similarity between two vectors."""
vec1 = np.array(vec1, dtype=float)
vec2 = np.array(vec2, dtype=float)
dot_product = np.dot(vec1, vec2)
norm_vec1 = np.linalg.norm(vec1)
norm_vec2 = np.linalg.norm(vec2)
return dot_product / (norm_vec1 * norm_vec2)
def find_similar_items(input_embedding, embeddings, threshold=0.5, top_k=2):
"""Find the most similar items based on cosine similarity."""
# Calculate cosine similarity between the input embedding and all other embeddings
similarities = [(index, cosine_similarity_manual(input_embedding, vec)) for index, vec in enumerate(embeddings)]
# Filter out any similarities below the threshold
filtered_similarities = [(index, sim) for index, sim in similarities if sim >= threshold]
# Sort the filtered similarities by similarity score
sorted_indices = sorted(filtered_similarities, key=lambda x: x[1], reverse=True)[:top_k]
# Return the top-k most similar items
return sorted_indices
def find_matching_items_with_rag(df_items, item_descs):
"""Take the input item descriptions and find the most similar items based on cosine similarity for each description."""
# Select the embeddings from the DataFrame.
embeddings = df_items['embeddings'].tolist()
similar_items = []
for desc in item_descs:
# Generate the embedding for the input item
input_embedding = get_embeddings([desc])
# Find the most similar items based on cosine similarity
similar_indices = find_similar_items(input_embedding, embeddings, threshold=0.6)
similar_items += [df_items.iloc[i] for i in similar_indices]
return similar_items
分析模組
在本模組中,我們利用gpt-4o-mini
分析輸入影像並提取重要特徵,例如詳細描述、樣式和類型。分析是透過簡單的 API 呼叫執行的,我們提供用於分析的圖像的 URL 並請求模型識別相關特徵。
為了確保模型返回準確的結果,我們在提示中使用特定技術:
- 輸出格式規格:我們指示模型傳回具有預先定義結構的 JSON 區塊,其中包括:
items
(str[]):字串列表,每個字串代表一件衣服的簡潔標題,包括風格、顏色和性別。這些標題productDisplayName
與我們原始資料庫中的屬性非常相似。category
(str):最能代表給定項目的類別。此模型articleTypes
從原始樣式資料框中存在的所有唯一的清單中進行選擇。gender
(str):指示該項目的目標性別的標籤。該模型從選項中進行選擇[Men, Women, Boys, Girls, Unisex]
。
- 清晰簡潔的說明:
- 我們提供了關於專案標題應包含哪些內容以及輸出格式應是什麼的明確說明。輸出應為 JSON 格式,但不包含
json
模型回應通常包含的標籤。
- 我們提供了關於專案標題應包含哪些內容以及輸出格式應是什麼的明確說明。輸出應為 JSON 格式,但不包含
- 一擊範例:
- 為了進一步闡明預期輸出,我們為模型提供了範例輸入描述和相應的範例輸出。儘管這可能會增加使用的令牌數量(從而增加呼叫成本),但它有助於指導模型並帶來更好的整體效能。
透過遵循這種結構化方法,我們的目標是從gpt-4o-mini
模型中獲取精確且有用的信息,以便進一步分析並整合到我們的資料庫中。
def analyze_image(image_base64, subcategories):
response = client.chat.completions.create(
model=GPT_MODEL,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": """Given an image of an item of clothing, analyze the item and generate a JSON output with the following fields: "items", "category", and "gender".
Use your understanding of fashion trends, styles, and gender preferences to provide accurate and relevant suggestions for how to complete the outfit.
The items field should be a list of items that would go well with the item in the picture. Each item should represent a title of an item of clothing that contains the style, color, and gender of the item.
The category needs to be chosen between the types in this list: {subcategories}.
You have to choose between the genders in this list: [Men, Women, Boys, Girls, Unisex]
Do not include the description of the item in the picture. Do not include the ```json ``` tag in the output.
Example Input: An image representing a black leather jacket.
Example Output: {"items": ["Fitted White Women's T-shirt", "White Canvas Sneakers", "Women's Black Skinny Jeans"], "category": "Jackets", "gender": "Women"}
""",
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_base64}",
},
}
],
}
]
)
# Extract relevant features from the response
features = response.choices[0].message.content
return features
使用範例圖像測試提示
為了評估提示的有效性,讓我們使用資料集中選擇的圖像載入並測試它。我們將使用該"data/sample_clothes/sample_images"
資料夾中的圖像,確保各種樣式、性別和類型。以下是所選樣本:
2133.jpg
: 男士襯衫7143.jpg
: 女式襯衫4226.jpg
: 休閒男士印花T卹
透過使用這些不同的圖像測試提示,我們可以評估其從不同類型的服裝和配件中準確分析和提取相關特徵的能力。
我們需要一個實用函數來將 .jpg 圖片編碼為 base64
import base64
def encode_image_to_base64(image_path):
with open(image_path, 'rb') as image_file:
encoded_image = base64.b64encode(image_file.read())
return encoded_image.decode('utf-8')
# Set the path to the images and select a test image
image_path = "data/sample_clothes/sample_images/"
test_images = ["2133.jpg", "7143.jpg", "4226.jpg"]
# Encode the test image to base64
reference_image = image_path + test_images[0]
encoded_image = encode_image_to_base64(reference_image)
# Select the unique subcategories from the DataFrame
unique_subcategories = styles_df['articleType'].unique()
# Analyze the image and return the results
analysis = analyze_image(encoded_image, unique_subcategories)
image_analysis = json.loads(analysis)
# Display the image and the analysis results
display(Image(filename=reference_image))
print(image_analysis)
接下來,我們處理影像分析的輸出,並使用它來過濾和顯示資料集中的匹配項目。這是程式碼的細分:
- 提取圖像分析結果:我們從詞典中提取項目描述、類別和性別
image_analysis
。 - 過濾資料集:我們過濾
styles_df
DataFrame 以僅包含與影像分析中的性別相符(或男女皆宜)的項目,並排除與分析影像相同類別的項目。 - 尋找匹配項目:我們使用該
find_matching_items_with_rag
函數在過濾後的資料集中尋找與從分析圖像中提取的描述相符的項目。 - 顯示匹配項:我們建立一個 HTML 字串來顯示匹配項的圖像。我們使用專案 ID 建立圖像路徑並將每個圖像附加到 HTML 字串。最後,我們用來
display(HTML(html))
渲染筆記本中的圖像。
本單元有效地示範如何使用影像分析結果來篩選資料集並直觀地顯示與分析影像特徵相符的項目。
# Extract the relevant features from the analysis
item_descs = image_analysis['items']
item_category = image_analysis['category']
item_gender = image_analysis['gender']
# Filter data such that we only look through the items of the same gender (or unisex) and different category
filtered_items = styles_df.loc[styles_df['gender'].isin([item_gender, 'Unisex'])]
filtered_items = filtered_items[filtered_items['articleType'] != item_category]
print(str(len(filtered_items)) + " Remaining Items")
# Find the most similar items based on the input item descriptions
matching_items = find_matching_items_with_rag(filtered_items, item_descs)
# Display the matching items (this will display 2 items for each description in the image analysis)
html = ""
paths = []
for i, item in enumerate(matching_items):
item_id = item['id']
# Path to the image file
image_path = f'data/sample_clothes/sample_images/{item_id}.jpg'
paths.append(image_path)
html += f'<img src="{image_path}" style="display:inline;margin:1px"/>'
# Print the matching item description as a reminder of what we are looking for
print(item_descs)
# Display the image
display(HTML(html))
保護措施
在使用像 GPT-4o mini 這樣的大型語言模型(LLMs)時,「保護措施(guardrails)」 指的是為確保模型輸出保持在預期參數或界限內而設置的機制或檢查。這些保護措施對於維持模型回應的質量和相關性非常重要,特別是在處理複雜或微妙的任務時。
設置保護措施的原因包括以下幾點:
- 準確性:它們有助於確保模型的輸出準確且與所提供的輸入相關。
- 一致性:它們保持模型回應的一致性,特別是在處理類似或相關的輸入時。
- 安全性:它們可以防止模型產生有害的、攻擊性的或不適當的內容。
- 上下文相關性:它們確保模型的輸出與其所使用的特定任務或領域在上下文上相關。
在我們的案例中,我們使用 GPT-4o mini 來分析時尚圖片並建議與原始服裝相輔相成的單品。為了實施保護措施,我們可以對結果進行精煉:在從 GPT-4o mini 獲得初步建議後,我們可以將原始圖片和建議的單品再次發送給模型。然後,我們可以請求 GPT-4o mini 評估每個建議的單品是否確實適合原始服裝。
這樣的做法使模型能夠根據反饋或額外信息進行自我修正和調整其輸出。通過實施這些保護措施並啟用自我修正,我們可以增強模型在時尚分析和推薦方面輸出的可靠性和實用性。
為了促進這一過程,我們撰寫一個提示,要求 LLM 給出一個簡單的「是」或「否」回答,以判斷建議的單品是否與原始服裝相匹配。這種二元回應有助於簡化精煉過程,並確保從模型獲得明確且可行的反饋。
def check_match(reference_image_base64, suggested_image_base64):
response = client.chat.completions.create(
model=GPT_MODEL,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": """ You will be given two images of two different items of clothing.
Your goal is to decide if the items in the images would work in an outfit together.
The first image is the reference item (the item that the user is trying to match with another item).
You need to decide if the second item would work well with the reference item.
Your response must be a JSON output with the following fields: "answer", "reason".
The "answer" field must be either "yes" or "no", depending on whether you think the items would work well together.
The "reason" field must be a short explanation of your reasoning for your decision. Do not include the descriptions of the 2 images.
Do not include the ```json ``` tag in the output.
""",
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{reference_image_base64}",
},
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{suggested_image_base64}",
},
}
],
}
],
max_tokens=300,
)
# Extract relevant features from the response
features = response.choices[0].message.content
return features
最後,讓我們確定上面確定的哪些物品真正與這套服裝相得益彰。
# Select the unique paths for the generated images
paths = list(set(paths))
for path in paths:
# Encode the test image to base64
suggested_image = encode_image_to_base64(path)
# Check if the items match
match = json.loads(check_match(encoded_image, suggested_image))
# Display the image and the analysis results
if match["answer"] == 'yes':
display(Image(filename=path))
print("The items match!")
print(match["reason"])
我們可以觀察到,最初的潛在物品清單已被進一步細化,從而產生了更精心策劃的選擇,與服裝完美契合。此外,該模型還解釋了為什麼每個項目被認為是很好的匹配,為決策過程提供了寶貴的見解。
結論
在這個 Jupyter Notebook 中,我們探討了 GPT-4o mini 及其他機器學習技術在時尚領域的應用。我們展示了如何分析服裝圖片、提取相關特徵,並利用這些資訊找到與原始服裝相輔相成的搭配單品。透過實施保護措施和自我修正機制,我們精煉了模型的建議,以確保它們的準確性和情境相關性。
這種方法在現實世界中具有多種實用用途,包括:
- 個人化購物助理:零售商可以利用這項技術向顧客提供個人化的服裝推薦,增強購物體驗並提高顧客滿意度。
- 虛擬衣櫃應用程式:使用者可以上傳自己服裝的圖像來建立虛擬衣櫃,並接收與現有衣服相符的新物品的建議。
- 時裝設計和造型:時裝設計師和造型師可以使用此工具嘗試不同的組合和風格,從而簡化創作過程。
然而,需考慮的一個因素是成本。使用大型語言模型(LLMs)和圖像分析模型可能會產生成本,尤其是在頻繁使用的情況下。因此,考慮這些技術的成本效益非常重要。GPT-4o mini 的定價為每 1,000 個 tokens $0.01,這意味著處理一張 256px x 256px 的圖像需要約 $0.00255。
總的來說,這個 Notebook 為時尚與 AI 交集的進一步探索和開發奠定了基礎,為個性化和智能化的服裝推薦系統打開了大門。
資料來源: https://cookbook.openai.com/examples/how_to_combine_gpt4o_with_rag_outfit_assistant