前言
前面我们介绍了很多大模型的理论知识,今天来实践一下。
我们要做的功能是图片整理,在生活中我们会不断的拍照,直到几百上千张后,我们已经很难找到前面的某张图片,这时候就需要对图片进行分类整理,才能方便使用。
数据准备
避免泄露隐私,我直接文生图搞了一些照片:
分组情况:
- 大山里旅行:0,1,4,6,7,8,18
- 生活中的美食:3,5,9,13,19
- 会议和工作:10,12,17,20
- 各种动漫:2,11,14,15,16
DBScan自动聚类
首先我们能想到的最简单的整理方式就是,直接用无监督学习中的聚类。
先将提取图片的特征:
- VIT:Vision Transformer (ViT) 是一种基于 Transformer 架构的图像特征提取模型。它不同于传统的卷积神经网络(CNN),使用了自注意力机制来处理图像数据。
import PIL.Image
import torch
from transformers import ViTModel, ViTImageProcessor
extractor = ViTImageProcessor.from_pretrained("google/vit-base-patch16-224-in21k")
vit_model = ViTModel.from_pretrained("google/vit-base-patch16-224-in21k")
def load_imgs_vit_embedding(dir: str):
x, y = [], []
for file in os.listdir(dir):
if not file.endswith(".jpg"):
continue
img = PIL.Image.open(f"{dir}/{file}")
img_input = extractor(images=img, return_tensors="pt")
with torch.no_grad():
output = vit_model(**img_input).last_hidden_state.mean(dim=1).reshape(-1, )
x.append(output.numpy())
y.append(file)
return x, y
再用DBScan分类:
from sklearn.cluster import DBSCAN
x, y = vit.load_imgs_vit_embedding("./imgs")
dbsy = DBSCAN(eps=5, min_samples=2).fit_predict(x)
方便查看聚类的效果,我们画一下特征在三维空间的PCA降维后的坐标图像:
fig = plt.figure()
pcax = PCA(n_components=3).fit_transform(x)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(pcax[:, 0], pcax[:, 1], pcax[:, 2], c=dbsy)
plt.show()
- 可以看到同样分类的基本都聚在一起了。
打印一下具体的分组结果,除了噪点外,其他分类还可以。
噪点【-1】:['14.jpg', '13.jpg']
分组【0】:['8.jpg', '18.jpg', '4.jpg', '7.jpg', '6.jpg', '1.jpg', '0.jpg']
分组【1】:['9.jpg', '19.jpg', '5.jpg', '3.jpg']
分组【2】:['15.jpg', '16.jpg', '11.jpg', '2.jpg']
分组【3】:['17.jpg', '12.jpg', '10.jpg', '20.jpg']
CLIP召回
如果我们只对一类照片感兴趣,比如我想在所有的拍摄的照片中,找到在山上旅游的照片。
这时候我们就需要根据一段文字描述找到特征最相似的图片。
这里我们用clip算法
CLIP(Contrastive Language-Image Pretraining)是由OpenAI提出的一种联合图像和文本的预训练模型。CLIP通过对比学习的方法,将图像和文本嵌入到同一个向量空间中,使得相关的图像和文本具有相似的向量表示。这使得CLIP在图像和文本的跨模态任务中表现出色,如图像分类、图像检索、文本生成图像等。
代码实现:
import numpy as np
import torch
from transformers import CLIPModel, CLIPProcessor
clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
x, y = data_process.load_images("./imgs")
text_input = clip_processor(text=["一个人在大山旅行"], images=x, return_tensors="pt", padding=True)
# logits_per_text 是文本特征和图片特征的相似度。
with torch.no_grad():
output = clip_model(**text_input).logits_per_text.softmax(dim=1).numpy().reshape(-1)
y_index = np.argsort(-output)
ny = np.array(y)
print(ny[y_index])
打印结果:
['8.jpg' '1.jpg' '4.jpg' '18.jpg' '6.jpg' '16.jpg' '13.jpg' '7.jpg'
'0.jpg' '17.jpg' '9.jpg' '14.jpg' '3.jpg' '5.jpg' '19.jpg' '2.jpg'
'11.jpg' '20.jpg' '12.jpg' '15.jpg' '10.jpg']
对比上面我们的正确结果,召回准确率还是很高的。
基于大模型的方法
思考:我们在给图片分类时,还是需要考虑一些非图片的特征,比如照片的拍摄时间,拍摄地点,人文,节日等等关键信息。
比如,召回的输入是:国庆时在上海的旅游照片。这时只从图片特征处理是无法处理的。这时候就需要我们使用大模型的能力来处理:
- 图片信息扩展,我们将图片的内容转换为文字描述,然后和它附带的标签(时间,地点,人物,时间)一起放到查询引擎中。并提取记录它们的特征。
- 使用llm将用户查询的query,转换为查询条件和召回特征。
- 先检索,再在检索集中进行相似度匹配。
图片转文本
这一步可以使用大模型来完成,如下,用千问的vl模型来处理:
from dashscope import MultiModalConversation
def get_image_desc(img_path: str):
messages = [{
'role': 'system',
'content': [{
'text': 'You are a helpful assistant.'
}]
}, {
'role':
'user',
'content': [
{
'image': img_path
},
{
'text': '描述图片中的内容'
},
]
}]
response = MultiModalConversation.call(model='qwen-vl-plus', messages=messages)
return response.output.choices[0].message.content
文本向量化
上面用的clip的模型,能处理的文本长度是非常有限的,还是推荐用文本embedding大模型来处理。
如下还是用千问模型来处理。
def embed_with_str(query: str):
resp = dashscope.TextEmbedding.call(
model=dashscope.TextEmbedding.Models.text_embedding_v1,
input=query)
if resp.status_code == HTTPStatus.OK:
return resp.output["embeddings"][0]["embedding"]
else:
return []
输入扩展
略
其他
具体的链路还比较复杂,这里就不写了
尾语
大模型应用才刚刚起步,前途还大有可为。只要肯思考,万物皆AI。