原文地址:https://www.superteams.ai/blog/automatic-product-inventory-and-recommendation-engine-using-clip-and-qdrant
假设您是一个电子商务平台,库存中有许多不同类别的产品。每次新产品进入您的库存时,您都希望它自动分类到预定义的类别之一。此外,您还想知道您的库存中有哪些类似的产品,以便跟踪您拥有的商品。这种类型的系统对于电子商务平台非常有用,因为它简化了产品分类流程,节省时间和人力,并实现高效的库存管理。
此外,通过识别类似产品,您可以更好地了解您的产品分类,识别潜在的差距或冗余,并就产品采购、定价和促销做出明智的决策。通过这种方式,您可以优化库存水平,最大限度地减少缺货和库存过剩。

在 Superteams.ai,我们决定接受这个问题陈述并创建一个人工智能驱动的解决方案来解决它。我们设计了一个系统,将产品图像分类为各自的类别,然后建议库存中已有的类似产品。
为了实现这一目标,我们利用了两项尖端技术:OpenAI 的 CLIP 模型和 Qdrant 矢量数据库。

OpenAI 的 CLIP(对比语言图像预训练)模型是人工智能领域的游戏规则改变者。这个强大的模型在共享向量空间中生成文本和图像嵌入,从而实现文本概念和视觉表示之间的无缝映射。神奇之处在于该模型能够将语义相似的文本和图像紧密地放置在向量空间内。这意味着图像及其相应的文本描述自然对齐,使得将图像与其各自的类别相匹配变得非常容易。
CLIP 如何嵌入不同图像和文本的演示
Qdrant虽然 CLIP 模型为我们的解决方案奠定了基础,但 Qdrant 矢量数据库将其提升到了一个新的水平。Qdrant 是一个开源数据库,专门用于以卓越的速度和效率存储和检索向量嵌入及其相关元数据。其先进的相似性搜索功能使我们能够快速探索广阔的向量空间并从数据库语料库中检索最相关的信息。
通过利用 Qdrant,我们可以根据存储的向量嵌入快速查找用户查询。这意味着,当上传新产品图片时,我们的系统可以快速识别您的库存中已有的最相似的产品,使您能够就产品分类和库存管理做出明智的决策。
工作流程我们使用了Kaggle 的时尚产品数据集。该数据集包含约 44,000 张各种时尚产品及其各自产品类别的图像。大约有143种类别。它们看起来像这样:
短裤
裤子
雨裤
毛衣
纱丽服
耸耸肩
运动凉鞋
…….
手镯
沐浴露和磨砂膏
袖珍的
树干
男士美容套装
拳击手
连衣裤
遮瑕膏
除臭剂
帽子
高跟鞋
头巾
钱包
免费礼品
第一步是创建这些类别的 CLIP 嵌入,并将它们存储在 Qdrant 中名为“文本嵌入(Text-Embeddings)”的集合中。然后,对于我们的推荐系统,我们正在创建另一个名为“图像嵌入(Image-Embeddings)”的集合。我们将计算所有 44,000 个产品图像的嵌入并将它们存储在集合中。用户上传产品图像,我们计算图像的嵌入。将此查询嵌入与“Text-Embeddings”集合的相似性搜索产生产品上传图像的类别。使用“Image-Embeddings”集合对查询嵌入进行相似性搜索,给出与上传的产品图像相似的库存产品图像。
工作流程图
让我们看看代码安装所需的软件包。
pip install torch transformers qdrant-client gradio pillow pandas kaggle
下载数据集:
kaggle datasets download -d paramaggarwal/fashion-product-images-small
提取产品类别。
import pandas as pd# Replace 'path_to_csv' with the actual path to your CSV filecsv_file_path = 'styles.csv'# Read the CSV file into a DataFramedf = pd.read_csv(csv_file_path, on_bad_lines='skip')# Now 'df' holds the DataFrame object with the data from the CSV fileprint(df)Item_List = list(set(df['articleType'].tolist()))
加载 CLIP 模型:
from transformers import CLIPProcessor, CLIPModelimport torchmodel_id = "openai/clip-vit-base-patch32"processor = CLIPProcessor.from_pretrained(model_id)model = CLIPModel.from_pretrained(model_id)# move model to device if possibledevice = 'cuda' if torch.cuda.is_available() else 'cpu'model.to(device)
计算类别文本的嵌入:
tokens = processor( text=Item_List, padding=True, images=None, return_tensors='pt').to(device)tokens.keys()text_emb = model.get_text_features( tokens)text_emb_list = text_emb.detach().cpu().numpy().tolist()
在本地主机上启动 Qdrant 实例:
docker run -p 6333:6333 -p 6334:6334 \ -v $(pwd)/qdrant_storage:/qdrant/storage:z \ qdrant/qdrant
将嵌入更新插入到名为“text_embeddings”的集合中。我们还添加了有效负载元数据以及嵌入。元数据包含产品类别的文本标签。稍后我们将使用此元数据来检索产品类别。
from qdrant_client import QdrantClient, modelsclient = QdrantClient(url="http://localhost:6333")client.create_collection( collection_name="text_embeddings", vectors_config=models.VectorParams(size=512, distance=models.Distance.COSINE),)# Convert list into a list of dictionariespayload = [{'itemtype': item} for item in Item_List]# Show the resultprint(payload)# Create a list of IDs from 1 to the length of the items listid_list = [i for i in range(1, len(Item_List) + 1)]# Show the resultprint(id_list)client.upsert( collection_name="text_embeddings", points=models.Batch( ids=id_list, payloads=payload, vectors=text_emb_list, ),)
接下来,让我们为图像嵌入创建另一个集合。
client.delete_collection(collection_name="image_embeddings")client.create_collection( collection_name="image_embeddings", vectors_config=models.VectorParams(size=512, distance=models.Distance.COSINE),)
接下来,我们创建一个 Pandas 数据框来组织图像嵌入和关联的元数据(图像的文件路径)。
import osimport torchfrom PIL import Imageimport pandas as pd# Define the directory where the images are storedimage_directory = 'images'# Initialize an empty list to store the datadata = []# Loop over all files in the image directoryfor i, filename in enumerate(os.listdir(image_directory)): if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')): # Add any other file types if needed # Construct the full path to the image file file_path = os.path.join(image_directory, filename) with Image.open(file_path) as img: # Prepare the image for the model tokens = processor( text=None, images=img, return_tensors="pt" )["pixel_values"].to(device) # Get image embeddings from the model image_embeddings = model.get_image_features( tokens ) # Append the filename and embeddings to the data list data.append((filename, image_embeddings.detach().cpu().numpy().tolist()[0]) # Print the iteration number print(f'Iteration {i}: Processed {filename}') # Create a DataFrame with the datadf = pd.DataFrame(data, columns=['Filename', 'ImageEmbeddings'])# Display the DataFrameprint(df)In [ ]:# Assuming 'df' is your existing DataFrame# Create a new 'ID' column that starts at 1 and increments by 1 for each rowdf['ID'] = range(1, len(df) + 1)# Display the updated DataFrameprint(df)# Create the list of dictionaries with "Filename" as the keyfilenames_list = [{'Filename': filename} for filename in df['Filename']]# Create the list of ImageEmbeddings (assuming ImageTensor is already a list of embeddings)image_embeddings_list = df['ImageEmbeddings'].tolist()# Create the list of IDsids_list = df['ID'].tolist()
由于我们有大量图像嵌入(44,000 个产品图像),因此最好将其以 1000 个批次更新插入到 VectorDB 中,以提高速度和效率。
batch_size = 1000total_points = len(ids_list)for start_index in range(0, total_points, batch_size): # End index is the start of the next batch or the end of the list end_index = min(start_index + batch_size, total_points) # Slice the lists to create the current batch batch_ids = ids_list[start_index:end_index] batch_filenames = filenames_list[start_index:end_index] batch_image_embeddings = image_embeddings_list[start_index:end_index] # Upsert the current batch client.upsert( collection_name="image_embeddings", points=models.Batch( ids=batch_ids, payloads=batch_filenames, vectors=batch_image_embeddings, ), )
我们现在将开始为 Gradio UI 编写函数。第一个函数将 PIL 图像作为输入。然后,它使用文本嵌入集合执行相似性搜索并返回顶部结果,这基本上是产品的类别。
def image_classifier(image): # Prepare the image for the model tokens = processor( text=None, images=image, return_tensors="pt" )["pixel_values"].to(device) # Get image embeddings from the model image_embeddings = model.get_image_features( tokens ) query_vector = image_embeddings.detach().cpu().numpy().tolist()[0] record = client.search( collection_name="text_embeddings", query_vector=query_vector, limit=1, ) return record[0].payload['itemtype']
第二个函数将 PIL 图像作为输入,并通过对图像嵌入集合执行相似性搜索,以文件路径列表的形式返回前十个图像(来自库存)。
def image_path_list(image): # Prepare the image for the model tokens = processor( text=None, images=image, return_tensors="pt" )["pixel_values"].to(device) # Get image embeddings from the model image_embeddings = model.get_image_features( tokens ) query_vector = image_embeddings.detach().cpu().numpy().tolist()[0] record = client.search( collection_name="image_embeddings", query_vector=query_vector, limit=10, ) return [('fashion-dataset/fashion-dataset/images/' + element.payload['Filename'], None) for element in record]
Gradio UI 的代码:
import gradio as grwith gr.Blocks() as demo: with gr.Row(): upload_image = gr.Image(label= "Upload Your Image", type = 'pil') classifier_text = gr.Textbox(label= "Type of Item") with gr.Row(): image_gallery = gr.Gallery(label= "Similar items in the inventory", object_fit= 'contain', columns=[5], rows=[2],) with gr.Row(): clr_btn = gr.Button(value= "Clear") first_step = upload_image.upload(fn= image_classifier, inputs= upload_image, outputs= classifier_text) first_step.then(fn= image_path_list, inputs= upload_image, outputs = image_gallery) clr_btn.click(fn=lambda: (None, None, []), inputs=None, outputs=[upload_image, classifier_text, image_gallery]) demo.launch(share=True)
以下是代码的简要说明:
1. `with gr.Blocks() as demo:` 语句创建一个名为 `demo` 的 Gradio 接口。
2. 在“demo”块内,有三个“gr.Row()”组件,每个组件代表用户界面中的一行。
3. 第一行:
- `upload_image` 是一个 `gr.Image` 组件,允许用户上传图像。它有一个标签“上传您的图像”并接受 PIL(Python 图像库)图像。
- `classifier_text` 是一个 `gr.Textbox` 组件,它根据图像分类显示项目的类型。
4. 第二行:
- `image_gallery` 是一个 `gr.Gallery` 组件,用于显示库存中的类似项目。它有一个标签“库存中的类似商品”,并配置为显示 5 列和 2 行图像。
5. 第三行:
- `clr_btn` 是一个带有“Clear”标签的 `gr.Button` 组件。
6. “first_step”变量被赋值为“upload_image.upload()”的结果,当上传图片时会触发“image_classifier”函数。上传的图像作为函数的输入传递,输出显示在“classifier_text”文本框中。
7. 在“image_classifier”函数完成后,“first_step.then()”语句将另一个函数调用链接到“image_path_list”。它将上传的图像作为输入,并使用推荐图像路径的结果列表更新“image_gallery”。
8. `clr_btn.click()` 语句定义单击“清除”按钮时的行为。它将 `upload_image`、`classifier_text` 和 `image_gallery` 组件设置为其默认值(分别为 None、None 和空列表)。
9. 最后,“demo.launch(share=True)”启动 Gradio 界面并使其可共享,允许其他人通过生成的 URL 访问它。
结果以下是我们的用户界面的一些屏幕截图:
GitHub您可以访问此 Github 存储库中的代码:https://github.com/vardhanam/Product_Classifier_Recommendation/tree/main