GLM-4v-9b多模态教程:图文联合embedding生成、跨模态相似度计算与检索应用
GLM-4v-9b多模态教程:图文联合embedding生成、跨模态相似度计算与检索应用
1. 引言:为什么你需要关注图文联合embedding?
想象一下这个场景:你是一家电商公司的运营,每天要处理成千上万的商品图片和描述文案。你想快速找到所有“红色连衣裙”的图片,但系统只能通过文字标签搜索,很多图片没有打上“红色”或“连衣裙”的标签,结果漏掉了一大半。
或者,你是一个内容创作者,手头有一堆素材图片,想找一张“表达孤独感的城市夜景”配图,但光靠关键词搜索,出来的都是“夜景”、“城市”,完全不对味。
这就是传统搜索的痛点——文字和图片是割裂的。而GLM-4v-9b带来的图文联合embedding技术,正是为了解决这个问题。它能让机器像人一样,同时理解图片的内容和文字的含义,并把它们映射到同一个“语义空间”里进行比较。
简单来说,有了它,你可以:
- 用一段文字描述,直接搜到最匹配的图片
- 用一张图片,反向搜索到描述它的文字
- 实现真正的“以图搜图”、“以文搜图”,甚至“以图搜文”
本教程将手把手带你玩转GLM-4v-9b的这项核心能力。即使你之前没接触过多模态模型,跟着步骤走,也能在半小时内搭建起自己的跨模态检索系统。
2. 环境准备:一条命令搞定部署
开始之前,我们先确保环境就绪。GLM-4v-9b的部署已经非常简化,特别是通过CSDN星图这样的平台。
2.1 基础环境要求
你的机器需要满足以下条件:
- 操作系统:Linux (Ubuntu 20.04+ 推荐) 或 Windows (WSL2)
- 显卡:至少24GB显存 (如RTX 4090) 用于FP16精度运行;如果使用INT4量化,9GB显存 (如RTX 4060 Ti 16GB) 即可
- 内存:32GB RAM 或以上
- 磁盘空间:50GB 可用空间 (用于存放模型权重和依赖)
2.2 快速部署方案
对于大多数开发者,我推荐直接使用预置的Docker镜像或云平台服务,避免环境配置的麻烦。
方案一:使用CSDN星图镜像(最快) 如果你在CSDN星图平台,可以直接搜索“GLM-4v-9b”镜像,一键部署。平台已经预配置好了所有环境,包括transformers库、vLLM加速等。
方案二:本地Docker部署 如果你习惯本地开发,可以使用官方提供的Docker镜像:
# 拉取镜像
docker pull registry.cn-beijing.aliyuncs.com/glm/glm-4v-9b:latest
# 运行容器
docker run -it --gpus all \
-p 7860:7860 \
-v /path/to/your/data:/data \
registry.cn-beijing.aliyuncs.com/glm/glm-4v-9b:latest
方案三:手动安装(适合定制化需求) 如果你需要更灵活的控制,可以手动安装:
# 1. 创建虚拟环境
conda create -n glm4v python=3.10
conda activate glm4v
# 2. 安装PyTorch (根据你的CUDA版本选择)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
# 3. 安装transformers和视觉相关库
pip install transformers accelerate pillow
pip install git+https://github.com/huggingface/transformers
# 4. 安装vLLM用于加速推理(可选但推荐)
pip install vllm
2.3 验证安装
安装完成后,用一段简单的代码测试环境是否正常:
import torch
from transformers import AutoModel, AutoTokenizer
# 检查CUDA是否可用
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"GPU count: {torch.cuda.device_count()}")
if torch.cuda.is_available():
print(f"Current GPU: {torch.cuda.get_device_name(0)}")
print(f"GPU memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.2f} GB")
如果一切正常,你会看到类似这样的输出:
CUDA available: True
GPU count: 1
Current GPU: NVIDIA GeForce RTX 4090
GPU memory: 24.00 GB
3. 核心概念:图文embedding到底是什么?
在深入代码之前,我们先花几分钟搞清楚几个关键概念。别担心,我用最直白的方式解释。
3.1 什么是embedding?
你可以把embedding理解成一种“数学指纹”。就像每个人有独特的指纹一样,每段文字、每张图片经过模型处理,都会生成一串数字(通常是512或1024维的向量),这串数字就是它的embedding。
关键点在于:语义相似的内容,它们的embedding在数学空间里的距离也很近。
举个例子:
- “猫”的embedding和“猫咪”的embedding会很接近
- “狗”的embedding和“宠物”的embedding也比较接近
- 一张猫的图片和“猫”这个文字的embedding也会很接近
这就是跨模态检索的基础——不同模态(图、文)但语义相同的内容,被映射到了相近的位置。
3.2 GLM-4v-9b如何生成联合embedding?
GLM-4v-9b的巧妙之处在于,它用一个模型同时处理图片和文字:
- 图片处理:通过视觉编码器(ViT)把图片转换成视觉特征
- 文字处理:通过语言模型把文字转换成文本特征
- 特征融合:通过交叉注意力机制,让图片特征和文字特征相互“交流”
- 统一输出:最终输出一个融合了图文信息的联合embedding
这个过程是端到端训练的,意味着模型在学习过程中就学会了如何让图片和文字的embedding在同一个空间里对齐。
3.3 为什么1120×1120分辨率很重要?
你可能会注意到GLM-4v-9b特别强调了1120×1120的高分辨率支持。这在实际应用中非常实用:
- 小字识别:图表中的坐标轴标签、表格里的数字都能看清
- 细节保留:图片中的纹理、物体的细微特征不会丢失
- 文档处理:扫描的PDF、截图中的文字都能准确识别
传统模型往往把图片缩放到224×224或384×384,很多细节就糊掉了。GLM-4v-9b保持高分辨率输入,让它在处理复杂图片时优势明显。
4. 实战开始:生成你的第一个图文embedding
理论讲完了,现在我们来动手实践。我会带你一步步生成图片和文字的embedding。
4.1 加载模型和处理器
首先,我们需要加载GLM-4v-9b模型和对应的处理器:
import torch
from PIL import Image
from transformers import AutoModel, AutoTokenizer, AutoProcessor
# 设置设备
device = "cuda" if torch.cuda.is_available() else "cpu"
# 加载模型和处理器
model_name = "THUDM/glm-4v-9b"
print("正在加载tokenizer...")
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
print("正在加载processor...")
processor = AutoProcessor.from_pretrained(model_name, trust_remote_code=True)
print("正在加载模型...")
model = AutoModel.from_pretrained(
model_name,
torch_dtype=torch.float16, # 使用半精度减少显存占用
device_map="auto", # 自动分配到可用GPU
trust_remote_code=True
)
model.eval() # 设置为评估模式
print("模型加载完成!")
重要提示:第一次运行时会下载模型权重(约18GB),请确保网络通畅和磁盘空间充足。如果下载慢,可以考虑先下载到本地再加载。
4.2 准备测试数据
我们来准备一些测试用的图片和文字。你可以用自己的图片,或者用我提供的示例:
import requests
from io import BytesIO
import matplotlib.pyplot as plt
# 示例1:一张猫的图片
cat_image_url = "https://images.unsplash.com/photo-1514888286974-6d03bde4ba4f"
cat_text = "一只橘猫坐在窗台上晒太阳"
# 示例2:一张风景图片
landscape_image_url = "https://images.unsplash.com/photo-1501854140801-50d01698950b"
landscape_text = "山间湖泊,远处有雪山,水面平静如镜"
# 示例3:一张食物图片
food_image_url = "https://images.unsplash.com/photo-1565958011703-44f9829ba187"
food_text = "一盘新鲜的沙拉,有西红柿、黄瓜和生菜"
def download_image(url):
"""下载图片并转换为PIL Image"""
response = requests.get(url)
img = Image.open(BytesIO(response.content))
return img
# 下载图片
print("下载测试图片...")
cat_img = download_image(cat_image_url)
landscape_img = download_image(landscape_image_url)
food_img = download_image(food_image_url)
# 显示图片(可选)
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(cat_img)
axes[0].set_title("猫")
axes[0].axis('off')
axes[1].imshow(landscape_img)
axes[1].set_title("风景")
axes[1].axis('off')
axes[2].imshow(food_img)
axes[2].set_title("食物")
axes[2].axis('off')
plt.tight_layout()
plt.show()
4.3 生成图片embedding
现在我们来生成图片的embedding。GLM-4v-9b提供了专门的方法来提取视觉特征:
def get_image_embedding(image, model, processor):
"""
获取图片的embedding
参数:
image: PIL Image对象
model: 加载好的GLM-4v-9b模型
processor: 对应的处理器
返回:
embedding: 图片的embedding向量
"""
# 使用处理器准备输入
inputs = processor(images=image, return_tensors="pt")
# 将输入移动到GPU
inputs = {k: v.to(device) for k, v in inputs.items()}
# 生成embedding
with torch.no_grad(): # 不计算梯度,加快推理速度
outputs = model.get_image_features(**inputs)
# 提取embedding并归一化(重要!)
embedding = outputs.last_hidden_state.mean(dim=1) # 取平均池化
embedding = torch.nn.functional.normalize(embedding, p=2, dim=1) # L2归一化
return embedding.cpu().numpy() # 转回numpy方便后续处理
# 为三张图片生成embedding
print("生成图片embedding...")
cat_img_emb = get_image_embedding(cat_img, model, processor)
landscape_img_emb = get_image_embedding(landscape_img, model, processor)
food_img_emb = get_image_embedding(food_img, model, processor)
print(f"猫图片embedding形状: {cat_img_emb.shape}")
print(f"风景图片embedding形状: {landscape_img_emb.shape}")
print(f"食物图片embedding形状: {food_img_emb.shape}")
你会看到输出类似:
猫图片embedding形状: (1, 2048)
风景图片embedding形状: (1, 2048)
食物图片embedding形状: (1, 2048)
这里的2048就是embedding的维度。每个图片都被转换成了一个2048维的向量。
4.4 生成文字embedding
同样地,我们也可以为文字生成embedding:
def get_text_embedding(text, model, tokenizer):
"""
获取文字的embedding
参数:
text: 文本字符串
model: 加载好的GLM-4v-9b模型
tokenizer: 对应的tokenizer
返回:
embedding: 文本的embedding向量
"""
# 使用tokenizer编码文本
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True)
# 将输入移动到GPU
inputs = {k: v.to(device) for k, v in inputs.items()}
# 生成embedding
with torch.no_grad():
outputs = model.get_text_features(**inputs)
# 提取embedding并归一化
embedding = outputs.last_hidden_state.mean(dim=1) # 取平均池化
embedding = torch.nn.functional.normalize(embedding, p=2, dim=1) # L2归一化
return embedding.cpu().numpy()
# 为三段文字生成embedding
print("生成文字embedding...")
cat_text_emb = get_text_embedding(cat_text, model, tokenizer)
landscape_text_emb = get_text_embedding(landscape_text, model, tokenizer)
food_text_emb = get_text_embedding(food_text, model, tokenizer)
print(f"猫文字embedding形状: {cat_text_emb.shape}")
print(f"风景文字embedding形状: {landscape_text_emb.shape}")
print(f"食物文字embedding形状: {food_text_emb.shape}")
注意:文字embedding的维度也是2048,和图片embedding的维度一致。这是实现跨模态检索的关键——图片和文字在同一个向量空间里。
5. 计算相似度:让图片和文字“对话”
有了embedding,我们就可以计算相似度了。相似度越高,说明内容越相关。
5.1 理解相似度计算
在embedding空间里,我们通常用余弦相似度来衡量两个向量的相似程度。它的取值范围是[-1, 1]:
- 1:完全相似(方向相同)
- 0:不相关(垂直)
- -1:完全相反(方向相反)
计算公式很简单:两个向量的点积除以它们模长的乘积。
5.2 实现相似度计算
我们来写一个计算余弦相似度的函数:
import numpy as np
def cosine_similarity(vec1, vec2):
"""
计算两个向量的余弦相似度
参数:
vec1, vec2: 两个向量,形状为(1, n)或(n,)
返回:
similarity: 余弦相似度,范围[-1, 1]
"""
# 确保是1维向量
if len(vec1.shape) > 1:
vec1 = vec1.flatten()
if len(vec2.shape) > 1:
vec2 = vec2.flatten()
# 计算点积
dot_product = np.dot(vec1, vec2)
# 计算模长
norm1 = np.linalg.norm(vec1)
norm2 = np.linalg.norm(vec2)
# 避免除零
if norm1 == 0 or norm2 == 0:
return 0
# 计算余弦相似度
similarity = dot_product / (norm1 * norm2)
return similarity
# 测试:计算猫图片和猫文字的相似度
cat_similarity = cosine_similarity(cat_img_emb, cat_text_emb)
print(f"猫图片 vs 猫文字描述 相似度: {cat_similarity:.4f}")
# 计算猫图片和风景文字的相似度(应该较低)
cat_landscape_similarity = cosine_similarity(cat_img_emb, landscape_text_emb)
print(f"猫图片 vs 风景文字描述 相似度: {cat_landscape_similarity:.4f}")
正常情况下的输出应该是:
猫图片 vs 猫文字描述 相似度: 0.85xx (较高,接近1)
猫图片 vs 风景文字描述 相似度: 0.2xxx (较低,接近0)
5.3 批量计算相似度矩阵
在实际应用中,我们经常需要计算多个图片和多个文字之间的相似度。我们来写一个更实用的函数:
def compute_similarity_matrix(image_embeddings, text_embeddings):
"""
计算图片embedding和文字embedding之间的相似度矩阵
参数:
image_embeddings: 列表,包含多个图片的embedding
text_embeddings: 列表,包含多个文字的embedding
返回:
similarity_matrix: 相似度矩阵,shape=(n_images, n_texts)
"""
n_images = len(image_embeddings)
n_texts = len(text_embeddings)
# 初始化相似度矩阵
similarity_matrix = np.zeros((n_images, n_texts))
# 计算每对图片和文字的相似度
for i in range(n_images):
for j in range(n_texts):
similarity_matrix[i, j] = cosine_similarity(
image_embeddings[i], text_embeddings[j]
)
return similarity_matrix
# 准备数据
image_embeddings = [cat_img_emb, landscape_img_emb, food_img_emb]
text_embeddings = [cat_text_emb, landscape_text_emb, food_text_emb]
image_names = ["猫图片", "风景图片", "食物图片"]
text_descriptions = ["猫文字", "风景文字", "食物文字"]
# 计算相似度矩阵
similarity_matrix = compute_similarity_matrix(image_embeddings, text_embeddings)
print("相似度矩阵:")
print("行: 图片, 列: 文字")
print("=" * 50)
print(f"{'':15}", end="")
for text in text_descriptions:
print(f"{text:15}", end="")
print()
for i, img_name in enumerate(image_names):
print(f"{img_name:15}", end="")
for j in range(len(text_descriptions)):
print(f"{similarity_matrix[i, j]:.4f} ", end="")
print()
你会看到一个3x3的矩阵,对角线上的值应该最高(图片和对应的文字描述),非对角线上的值应该较低。
6. 构建跨模态检索系统
现在到了最实用的部分——用我们学到的知识构建一个真正的跨模态检索系统。
6.1 系统设计思路
一个完整的跨模态检索系统通常包括:
- 建库阶段:处理所有图片,生成embedding并存储
- 检索阶段:接收查询(文字或图片),计算相似度,返回最匹配的结果
- 排序阶段:按相似度排序,返回Top-K结果
6.2 实现图片库管理
我们先实现一个简单的图片库管理类:
import os
import pickle
from pathlib import Path
from typing import List, Dict, Tuple
import numpy as np
class ImageRetrievalSystem:
"""跨模态图片检索系统"""
def __init__(self, model, processor, tokenizer):
"""
初始化检索系统
参数:
model: GLM-4v-9b模型
processor: 图片处理器
tokenizer: 文本tokenizer
"""
self.model = model
self.processor = processor
self.tokenizer = tokenizer
self.image_paths = [] # 图片路径列表
self.image_embeddings = [] # 图片embedding列表
self.image_metadata = [] # 图片元数据(可选)
def add_image(self, image_path: str, image: Image.Image = None, metadata: Dict = None):
"""
添加图片到库中
参数:
image_path: 图片路径
image: PIL Image对象(如果提供则直接使用)
metadata: 图片元数据
"""
# 加载图片(如果未提供)
if image is None:
try:
image = Image.open(image_path).convert("RGB")
except Exception as e:
print(f"无法加载图片 {image_path}: {e}")
return False
# 生成embedding
try:
embedding = get_image_embedding(image, self.model, self.processor)
except Exception as e:
print(f"生成embedding失败 {image_path}: {e}")
return False
# 保存到库中
self.image_paths.append(image_path)
self.image_embeddings.append(embedding)
self.image_metadata.append(metadata or {})
print(f"已添加图片: {image_path} (embedding形状: {embedding.shape})")
return True
def add_image_directory(self, directory_path: str, extensions: List[str] = None):
"""
批量添加目录中的所有图片
参数:
directory_path: 目录路径
extensions: 支持的图片扩展名,默认为['.jpg', '.jpeg', '.png', '.bmp']
"""
if extensions is None:
extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.webp']
directory = Path(directory_path)
if not directory.exists():
print(f"目录不存在: {directory_path}")
return
image_files = []
for ext in extensions:
image_files.extend(directory.glob(f"*{ext}"))
image_files.extend(directory.glob(f"*{ext.upper()}"))
print(f"找到 {len(image_files)} 张图片")
added_count = 0
for img_path in image_files:
if self.add_image(str(img_path)):
added_count += 1
print(f"成功添加 {added_count} 张图片到库中")
def search_by_text(self, query_text: str, top_k: int = 5):
"""
用文字搜索图片
参数:
query_text: 查询文本
top_k: 返回最相似的K个结果
返回:
results: 列表,每个元素为(图片路径, 相似度, 元数据)
"""
# 生成查询文本的embedding
query_embedding = get_text_embedding(query_text, self.model, self.tokenizer)
# 计算相似度
similarities = []
for img_emb in self.image_embeddings:
sim = cosine_similarity(query_embedding, img_emb)
similarities.append(sim)
# 获取Top-K结果
similarities = np.array(similarities).flatten()
top_indices = np.argsort(similarities)[-top_k:][::-1] # 从高到低排序
# 构建结果
results = []
for idx in top_indices:
results.append((
self.image_paths[idx],
similarities[idx],
self.image_metadata[idx]
))
return results
def search_by_image(self, query_image: Image.Image, top_k: int = 5):
"""
用图片搜索图片(以图搜图)
参数:
query_image: 查询图片
top_k: 返回最相似的K个结果
返回:
results: 列表,每个元素为(图片路径, 相似度, 元数据)
"""
# 生成查询图片的embedding
query_embedding = get_image_embedding(query_image, self.model, self.processor)
# 计算相似度
similarities = []
for img_emb in self.image_embeddings:
sim = cosine_similarity(query_embedding, img_emb)
similarities.append(sim)
# 获取Top-K结果
similarities = np.array(similarities).flatten()
top_indices = np.argsort(similarities)[-top_k:][::-1]
# 构建结果
results = []
for idx in top_indices:
results.append((
self.image_paths[idx],
similarities[idx],
self.image_metadata[idx]
))
return results
def save_index(self, filepath: str):
"""
保存索引到文件
参数:
filepath: 保存路径
"""
data = {
'image_paths': self.image_paths,
'image_embeddings': self.image_embeddings,
'image_metadata': self.image_metadata
}
with open(filepath, 'wb') as f:
pickle.dump(data, f)
print(f"索引已保存到: {filepath}")
def load_index(self, filepath: str):
"""
从文件加载索引
参数:
filepath: 索引文件路径
"""
with open(filepath, 'rb') as f:
data = pickle.load(f)
self.image_paths = data['image_paths']
self.image_embeddings = data['image_embeddings']
self.image_metadata = data['image_metadata']
print(f"已加载索引,包含 {len(self.image_paths)} 张图片")
6.3 使用检索系统
现在我们来实际使用这个检索系统:
# 初始化检索系统
retrieval_system = ImageRetrievalSystem(model, processor, tokenizer)
# 添加一些测试图片(这里用之前下载的图片)
# 在实际应用中,你可以添加自己的图片目录
retrieval_system.add_image("cat_image.jpg", cat_img, {"category": "animal"})
retrieval_system.add_image("landscape_image.jpg", landscape_img, {"category": "scenery"})
retrieval_system.add_image("food_image.jpg", food_img, {"category": "food"})
# 用文字搜索图片
print("\n=== 文字搜索测试 ===")
query_text = "一只可爱的宠物"
results = retrieval_system.search_by_text(query_text, top_k=2)
print(f"查询: '{query_text}'")
print("搜索结果:")
for i, (path, score, metadata) in enumerate(results):
print(f" {i+1}. {Path(path).name} (相似度: {score:.4f}, 类别: {metadata.get('category', 'N/A')})")
# 用图片搜索图片(以图搜图)
print("\n=== 图片搜索测试 ===")
# 用猫图片搜索
results = retrieval_system.search_by_image(cat_img, top_k=2)
print("查询: 猫图片")
print("搜索结果:")
for i, (path, score, metadata) in enumerate(results):
print(f" {i+1}. {Path(path).name} (相似度: {score:.4f}, 类别: {metadata.get('category', 'N/A')})")
6.4 实际应用示例:电商商品搜索
让我们看一个更实际的例子——电商商品图片搜索:
# 模拟一个电商商品库
def setup_ecommerce_example():
"""设置电商商品搜索示例"""
# 创建新的检索系统
ecommerce_system = ImageRetrievalSystem(model, processor, tokenizer)
# 模拟添加商品图片(在实际应用中,这里应该是真实的商品图片)
# 我们使用一些示例图片URL来模拟
product_images = [
{
"path": "red_dress_1.jpg",
"url": "https://images.unsplash.com/photo-1595777457583-95e059d581b8",
"metadata": {"category": "clothing", "color": "red", "type": "dress", "price": 299}
},
{
"path": "blue_jeans_1.jpg",
"url": "https://images.unsplash.com/photo-1542272604-787c3835535d",
"metadata": {"category": "clothing", "color": "blue", "type": "jeans", "price": 199}
},
{
"path": "white_sneakers_1.jpg",
"url": "https://images.unsplash.com/photo-1549298916-b41d501d3772",
"metadata": {"category": "shoes", "color": "white", "type": "sneakers", "price": 599}
},
{
"path": "black_backpack_1.jpg",
"url": "https://images.unsplash.com/photo-1553062407-98eeb64c6a62",
"metadata": {"category": "bags", "color": "black", "type": "backpack", "price": 399}
},
{
"path": "red_dress_2.jpg",
"url": "https://images.unsplash.com/photo-1591047139829-d91aecb6caea",
"metadata": {"category": "clothing", "color": "red", "type": "dress", "price": 459}
}
]
print("正在构建电商商品库...")
for product in product_images:
try:
# 下载图片
img = download_image(product["url"])
# 添加到系统
ecommerce_system.add_image(
product["path"],
img,
product["metadata"]
)
except Exception as e:
print(f"添加商品失败 {product['path']}: {e}")
return ecommerce_system
# 设置电商示例
ecommerce_system = setup_ecommerce_example()
# 测试搜索
print("\n=== 电商商品搜索测试 ===")
# 测试1:搜索红色连衣裙
print("\n1. 搜索'红色连衣裙':")
results = ecommerce_system.search_by_text("红色连衣裙", top_k=3)
for i, (path, score, metadata) in enumerate(results):
print(f" {i+1}. {metadata.get('type', '未知')} - {metadata.get('color', '未知颜色')}色 "
f"(价格: ¥{metadata.get('price', 0)}, 相似度: {score:.4f})")
# 测试2:搜索休闲鞋
print("\n2. 搜索'休闲运动鞋':")
results = ecommerce_system.search_by_text("休闲运动鞋", top_k=3)
for i, (path, score, metadata) in enumerate(results):
print(f" {i+1}. {metadata.get('type', '未知')} - {metadata.get('color', '未知颜色')}色 "
f"(价格: ¥{metadata.get('price', 0)}, 相似度: {score:.4f})")
# 测试3:以图搜图(用红色连衣裙图片搜索)
print("\n3. 以图搜图(红色连衣裙):")
# 使用第一件红色连衣裙的图片进行搜索
red_dress_img = download_image("https://images.unsplash.com/photo-1595777457583-95e059d581b8")
results = ecommerce_system.search_by_image(red_dress_img, top_k=3)
for i, (path, score, metadata) in enumerate(results):
print(f" {i+1}. {metadata.get('type', '未知')} - {metadata.get('color', '未知颜色')}色 "
f"(价格: ¥{metadata.get('price', 0)}, 相似度: {score:.4f})")
这个示例展示了如何用GLM-4v-9b构建一个电商商品搜索系统。你可以看到,即使用户输入的是自然语言描述(如"红色连衣裙"),系统也能找到相关的商品图片。
7. 性能优化与实用技巧
在实际应用中,你可能会遇到性能问题。这里分享一些优化技巧:
7.1 批量处理加速
如果你需要处理大量图片,逐个处理会很慢。GLM-4v-9b支持批量处理:
def batch_get_image_embeddings(images, model, processor, batch_size=4):
"""
批量获取图片embedding
参数:
images: PIL Image对象列表
model: GLM-4v-9b模型
processor: 图片处理器
batch_size: 批处理大小
返回:
embeddings: embedding列表
"""
embeddings = []
# 分批处理
for i in range(0, len(images), batch_size):
batch = images[i:i+batch_size]
# 准备批处理输入
inputs = processor(images=batch, return_tensors="pt", padding=True)
inputs = {k: v.to(device) for k, v in inputs.items()}
# 批量生成embedding
with torch.no_grad():
outputs = model.get_image_features(**inputs)
# 处理每个样本
batch_embeddings = outputs.last_hidden_state
for j in range(len(batch)):
# 对每个样本取平均池化和归一化
emb = batch_embeddings[j].mean(dim=0, keepdim=True)
emb = torch.nn.functional.normalize(emb, p=2, dim=1)
embeddings.append(emb.cpu().numpy())
print(f"已处理 {min(i+batch_size, len(images))}/{len(images)} 张图片")
return embeddings
7.2 使用向量数据库
当图片数量很大时(比如超过1万张),用Python列表存储和搜索会变得很慢。这时应该使用专门的向量数据库:
# 使用FAISS向量数据库的示例
import faiss
class FaissRetrievalSystem:
"""使用FAISS的检索系统"""
def __init__(self, model, processor, tokenizer, embedding_dim=2048):
self.model = model
self.processor = processor
self.tokenizer = tokenizer
self.embedding_dim = embedding_dim
# 创建FAISS索引
self.index = faiss.IndexFlatIP(embedding_dim) # 使用内积(余弦相似度)
self.image_paths = []
self.image_metadata = []
def add_image(self, image_path, image=None, metadata=None):
"""添加图片到FAISS索引"""
if image is None:
image = Image.open(image_path).convert("RGB")
# 生成embedding
embedding = get_image_embedding(image, self.model, self.processor)
# 添加到FAISS
self.index.add(embedding.astype('float32'))
self.image_paths.append(image_path)
self.image_metadata.append(metadata or {})
return True
def search_by_text(self, query_text, top_k=5):
"""用文字搜索"""
# 生成文字embedding
query_embedding = get_text_embedding(query_text, self.model, self.tokenizer)
# FAISS搜索
distances, indices = self.index.search(query_embedding.astype('float32'), top_k)
# 构建结果
results = []
for i, idx in enumerate(indices[0]):
if idx < len(self.image_paths): # 确保索引有效
# FAISS返回的是内积,需要转换为余弦相似度
# 因为embedding已经归一化,内积就是余弦相似度
similarity = distances[0][i]
results.append((
self.image_paths[idx],
similarity,
self.image_metadata[idx]
))
return results
FAISS可以处理百万级别的向量,搜索速度极快。对于生产环境,推荐使用FAISS、Milvus、Qdrant等专业的向量数据库。
7.3 缓存机制
生成embedding是比较耗时的操作,特别是对于大图片。我们可以实现缓存机制:
import hashlib
import json
from pathlib import Path
class CachedRetrievalSystem(ImageRetrievalSystem):
"""带缓存的检索系统"""
def __init__(self, model, processor, tokenizer, cache_dir=".embedding_cache"):
super().__init__(model, processor, tokenizer)
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(exist_ok=True)
def _get_cache_key(self, image_path):
"""生成缓存键:文件路径+修改时间"""
path = Path(image_path)
if not path.exists():
return None
# 使用文件路径和修改时间作为缓存键
mtime = path.stat().st_mtime
key_str = f"{path.absolute()}:{mtime}"
return hashlib.md5(key_str.encode()).hexdigest()
def add_image(self, image_path, image=None, metadata=None):
"""带缓存的添加图片"""
# 检查缓存
cache_key = self._get_cache_key(image_path)
cache_file = self.cache_dir / f"{cache_key}.pkl"
if cache_file.exists():
# 从缓存加载
with open(cache_file, 'rb') as f:
cached_data = pickle.load(f)
embedding = cached_data['embedding']
print(f"从缓存加载: {image_path}")
else:
# 生成新的embedding
if image is None:
image = Image.open(image_path).convert("RGB")
embedding = get_image_embedding(image, self.model, self.processor)
# 保存到缓存
with open(cache_file, 'wb') as f:
pickle.dump({'embedding': embedding}, f)
print(f"生成并缓存: {image_path}")
# 添加到系统
self.image_paths.append(image_path)
self.image_embeddings.append(embedding)
self.image_metadata.append(metadata or {})
return True
8. 常见问题与解决方案
在实际使用中,你可能会遇到一些问题。这里总结了一些常见问题和解决方法:
8.1 显存不足问题
问题:处理大图片或批量处理时显存不足。
解决方案:
- 使用量化模型:GLM-4v-9b支持INT4量化,显存占用从18GB降到9GB
- 减小批处理大小:将batch_size从4降到2或1
- 降低图片分辨率:虽然GLM-4v-9b支持1120×1120,但对于简单图片可以适当降低
- 使用CPU卸载:对于非常大的模型,可以使用accelerate库的CPU卸载功能
# 使用INT4量化
from transformers import BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)
model = AutoModel.from_pretrained(
model_name,
quantization_config=quantization_config, # 添加量化配置
device_map="auto",
trust_remote_code=True
)
8.2 相似度分数不理想
问题:某些图片和文字的相似度分数偏低,即使看起来相关。
解决方案:
- 检查embedding归一化:确保所有embedding都进行了L2归一化
- 调整相似度阈值:根据实际数据调整匹配阈值
- 使用更详细的文字描述:提供更丰富、更准确的描述
- 微调模型:在自己的数据上微调模型(需要训练数据)
8.3 处理速度慢
问题:生成embedding或搜索速度慢。
解决方案:
- 使用vLLM加速:GLM-4v-9b支持vLLM,可以大幅提升推理速度
- 预计算embedding:对于静态图片库,提前计算好所有embedding
- 使用向量数据库:对于大规模搜索,使用FAISS等专用数据库
- 异步处理:对于实时性要求不高的场景,使用异步队列处理
8.4 中文支持问题
问题:中文描述搜索效果不如英文。
解决方案:
- 使用中英混合描述:GLM-4v-9b对中英文都支持良好,可以混合使用
- 确保tokenizer正确加载:使用
trust_remote_code=True参数 - 检查文本编码:确保中文文本正确编码,没有乱码
9. 总结
通过本教程,你应该已经掌握了GLM-4v-9b在图文联合embedding生成和跨模态检索方面的核心用法。让我们回顾一下关键要点:
9.1 核心收获
-
理解了embedding的本质:embedding是内容的"数学指纹",语义相似的内容在embedding空间里距离相近。
-
掌握了GLM-4v-9b的核心能力:这个模型不仅能理解图片和文字,还能让它们在同一个语义空间里对齐,这是跨模态检索的基础。
-
学会了实际应用:从生成embedding、计算相似度,到构建完整的检索系统,你现在可以自己实现一个跨模态搜索应用了。
-
了解了优化技巧:批量处理、向量数据库、缓存机制等技巧能帮你处理大规模数据。
9.2 应用场景扩展
GLM-4v-9b的图文联合embedding技术可以应用在很多场景:
- 电商平台:商品图片搜索、相似商品推荐
- 内容管理:媒体库智能分类、自动打标签
- 教育领域:图文教材检索、习题匹配
- 医疗影像:病例图片与诊断报告关联
- 安防监控:嫌疑人图片与描述匹配
9.3 下一步建议
如果你想进一步深入:
- 尝试不同任务:除了检索,GLM-4v-9b还支持视觉问答、图像描述等任务
- 优化性能:在自己的数据集上微调模型,提升特定领域的准确率
- 构建完整应用:将检索系统集成到Web应用或移动App中
- 探索多模态RAG:结合检索增强生成,构建更智能的多模态问答系统
9.4 最后的话
多模态AI正在改变我们处理和理解信息的方式。GLM-4v-9b作为一个开源的高性能多模态模型,让开发者能够以较低的成本构建强大的跨模态应用。
记住,技术本身不是目的,解决实际问题才是。希望本教程能帮你快速上手,将GLM-4v-9b的能力应用到你的项目中。如果在实践中遇到问题,多查阅官方文档,多在社区交流,技术之路就是不断尝试和解决问题的过程。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐




所有评论(0)