Représentations multimodales : passer d'une modalité à une autre en IA
Unifiez le texte, les images et l'audio dans des espaces d'embedding partagés
Embeddings crois-modaux représentent une avancée majeure en intelligence artificielle, permettant de comprendre et de raisonner à travers différents types de données au sein d’un espace de représentation unifié.
Cette technologie alimente les applications multimodales modernes, allant de la recherche d’images à la génération de contenu.
Cette image provient de l’article :
CrossCLR : apprentissage contrastif crois-modal pour les représentations vidéo multimodales, par Mohammadreza Zolfaghari et al.
Comprendre les embeddings crois-modaux
Les embeddings crois-modaux sont des représentations vectorielles qui codent l’information provenant de différentes modalités – telles que le texte, les images, l’audio et la vidéo – dans un espace d’embedding partagé. Contrairement aux embeddings unimodaux traditionnels, les approches crois-modales apprennent une représentation unifiée où les concepts sémantiquement similaires se regroupent ensemble, indépendamment de leur format d’origine.
Qu’est-ce qu’un embedding crois-modal ?
Au cœur de ces embeddings crois-modaux se trouve la résolution d’un défi critique en IA : comment comparer et relier l’information à travers différents types de données. Un classifieur d’images traditionnel ne peut travailler qu’avec des images, tandis qu’un modèle de texte gère uniquement le texte. Les embeddings crois-modaux comblent ce fossé en projetant différentes modalités dans un espace vectoriel commun où :
- Une image de chat et le mot “chat” ont des vecteurs d’embedding similaires
- Les relations sémantiques sont préservées à travers les modalités
- Les métriques de distance (similarité cosinus, distance euclidienne) mesurent la similarité crois-modale
Cette représentation unifiée permet des capacités puissantes comme la recherche d’images à l’aide de requêtes textuelles, la génération de légendes à partir d’images, ou même la classification à zéro exemple sans entraînement spécifique à la tâche.
L’architecture derrière l’apprentissage crois-modal
Les systèmes crois-modaux modernes utilisent généralement des architectures à encodeurs doubles avec des objectifs d’apprentissage contrastif :
Encodeurs doubles : Des réseaux neuronaux séparés encodent chaque modalité. Par exemple, CLIP utilise :
- Un vision transformer (ViT) ou un ResNet pour les images
- Un encodeur de texte basé sur un transformer pour le langage
Apprentissage contrastif : Le modèle apprend en maximisant la similarité entre les paires correspondantes (par exemple, une image et sa légende) tout en minimisant la similarité entre les paires non correspondantes. La fonction de perte contrastive peut s’exprimer comme suit :
$$ \mathcal{L} = -\log \frac{\exp(\text{sim}(v_i, t_i) / \tau)}{\sum_{j=1}^{N} \exp(\text{sim}(v_i, t_j) / \tau)} $$
où $v_i$ est l’embedding de l’image, $t_i$ est l’embedding du texte, $\text{sim}$ est la similarité (généralement le cosinus), et $\tau$ est un paramètre de température.
Technologies et modèles clés
CLIP : Pionnier de la compréhension vision-langage
CLIP (Contrastive Language-Image Pre-training) d’OpenAI a révolutionné le domaine en s’entraînant sur 400 millions de paires image-texte provenant d’Internet. L’architecture du modèle se compose de :
import torch
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
# Charger le modèle CLIP pré-entraîné
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
# Préparer les entrées
image = Image.open("example.jpg")
texts = ["une photo d'un chat", "une photo d'un chien", "une photo d'un oiseau"]
# Processer et obtenir les embeddings
inputs = processor(text=texts, images=image, return_tensors="pt", padding=True)
# Obtenir les scores de similarité crois-modale
outputs = model(**inputs)
logits_per_image = outputs.logits_per_image
probs = logits_per_image.softmax(dim=1)
print(f"Probabilités : {probs}")
L’innovation clé de CLIP a été sa mise à l’échelle et sa simplicité. En s’entraînant sur des données à grande échelle d’Internet sans annotations manuelles, il a atteint des capacités remarquables de transfert à zéro exemple. Comment CLIP diffère-t-il des modèles de vision traditionnels ? Contrairement aux classificateurs supervisés entraînés sur des ensembles d’étiquettes fixes, CLIP apprend à partir de la supervision naturelle du langage, ce qui le rend adaptable à tout concept visuel décrivable en texte.
ImageBind : Étendre à six modalités
ImageBind de Meta étend les embeddings crois-modaux au-delà de la vision et du langage pour inclure :
- L’audio (bruits environnementaux, parole)
- Les informations de profondeur
- L’imagerie thermique
- Les données IMU (capteurs de mouvement)
Cela crée un espace d’embedding multimodal véritablement unifié où toutes les six modalités sont alignées. L’idée clé est que les images servent de “modalité de liaison” – en associant les images à d’autres modalités et en exploitant l’alignement existant entre la vision et le langage, ImageBind crée un espace unifié sans avoir besoin de toutes les paires possibles de modalités pendant l’entraînement.
Alternatives open source
L’écosystème s’est étendu avec plusieurs implémentations open source :
OpenCLIP : Une implémentation communautaire offrant des modèles plus grands et des recettes d’entraînement diverses :
import open_clip
model, _, preprocess = open_clip.create_model_and_transforms(
'ViT-L-14',
pretrained='laion2b_s32b_b82k'
)
tokenizer = open_clip.get_tokenizer('ViT-L-14')
Modèles LAION-5B : Entraînés sur le grand ensemble de données open source LAION-5B, offrant des alternatives aux modèles propriétaires avec des performances comparables ou meilleures.
Pour les développeurs intéressés par des solutions open source d’embeddings textuels d’avant-garde, les modèles Qwen3 Embedding & Reranker sur Ollama offrent d’excellentes performances multilingues avec un déploiement local facile.
Stratégies d’implémentation
Construction d’un système de recherche crois-modal
Une implémentation pratique des embeddings crois-modaux pour la recherche sémantique implique plusieurs composants. Quelles sont les principales applications des embeddings crois-modaux ? Elles alimentent des cas d’usage allant de la recherche de produits e-commerce à la modération de contenu et aux outils créatifs.
import numpy as np
from typing import List, Tuple
import faiss
from transformers import CLIPModel, CLIPProcessor
class CrossModalSearchEngine:
def __init__(self, model_name: str = "openai/clip-vit-base-patch32"):
self.model = CLIPModel.from_pretrained(model_name)
self.processor = CLIPProcessor.from_pretrained(model_name)
self.image_index = None
self.image_metadata = []
def encode_images(self, images: List) -> np.ndarray:
"""Encoder les images en embeddings"""
inputs = self.processor(images=images, return_tensors="pt", padding=True)
with torch.no_grad():
embeddings = self.model.get_image_features(**inputs)
return embeddings.cpu().numpy()
def encode_text(self, texts: List[str]) -> np.ndarray:
"""Encoder les requêtes textuelles en embeddings"""
inputs = self.processor(text=texts, return_tensors="pt", padding=True)
with torch.no_grad():
embeddings = self.model.get_text_features(**inputs)
return embeddings.cpu().numpy()
def build_index(self, image_embeddings: np.ndarray):
"""Construire un index FAISS pour une recherche efficace de similarité"""
dimension = image_embeddings.shape[1]
# Normaliser les embeddings pour la similarité cosinus
faiss.normalize_L2(image_embeddings)
# Créer l'index (en utilisant HNSW pour un déploiement à grande échelle)
self.image_index = faiss.IndexHNSWFlat(dimension, 32)
self.image_index.hnsw.efConstruction = 40
self.image_index.add(image_embeddings)
def search(self, query: str, k: int = 10) -> List[Tuple[int, float]]:
"""Rechercher des images à l'aide d'une requête textuelle"""
query_embedding = self.encode_text([query])
faiss.normalize_L2(query_embedding)
distances, indices = self.image_index.search(query_embedding, k)
return list(zip(indices[0], distances[0]))
# Exemple d'utilisation
engine = CrossModalSearchEngine()
# Construire l'index à partir de la collection d'images
image_embeddings = engine.encode_images(image_collection)
engine.build_index(image_embeddings)
# Rechercher avec du texte
results = engine.search("coucher de soleil sur les montagnes", k=5)
Ajustement fin pour des tâches spécifiques à un domaine
Bien que les modèles pré-entraînés fonctionnent bien pour les usages généraux, les applications spécifiques à un domaine bénéficient d’un ajustement fin :
from transformers import CLIPModel, CLIPProcessor, AdamW
import torch.nn as nn
class FineTuneCLIP:
def __init__(self, model_name: str, num_epochs: int = 10):
self.model = CLIPModel.from_pretrained(model_name)
self.processor = CLIPProcessor.from_pretrained(model_name)
self.num_epochs = num_epochs
def contrastive_loss(self, image_embeddings, text_embeddings, temperature=0.07):
"""Calculer la perte contrastive InfoNCE"""
# Normaliser les embeddings
image_embeddings = nn.functional.normalize(image_embeddings, dim=1)
text_embeddings = nn.functional.normalize(text_embeddings, dim=1)
# Calculer la matrice de similarité
logits = torch.matmul(image_embeddings, text_embeddings.T) / temperature
# Les étiquettes sont diagonales (paires correspondantes)
labels = torch.arange(len(logits), device=logits.device)
# Perte symétrique
loss_i = nn.functional.cross_entropy(logits, labels)
loss_t = nn.functional.cross_entropy(logits.T, labels)
return (loss_i + loss_t) / 2
def train(self, dataloader, learning_rate=5e-6):
"""Ajuster fin sur des données spécifiques à un domaine"""
optimizer = AdamW(self.model.parameters(), lr=learning_rate)
self.model.train()
for epoch in range(self.num_epochs):
total_loss = 0
for batch in dataloader:
images, texts = batch['images'], batch['texts']
# Processer les entrées
inputs = self.processor(
text=texts,
images=images,
return_tensors="pt",
padding=True
)
# Passage avant
outputs = self.model(**inputs, return_loss=True)
loss = outputs.loss
# Passage arrière
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(dataloader)
print(f"Époque {epoch+1}/{self.num_epochs}, Perte : {avg_loss:.4f}")
Considérations pour le déploiement en production
Optimisation des performances d’inférence
Comment puis-je optimiser les embeddings crois-modaux pour la production ? L’optimisation des performances est cruciale pour le déploiement en production :
Quantification du modèle : Réduire la taille du modèle et augmenter la vitesse d’inférence :
import torch
from torch.quantization import quantize_dynamic
# Quantification dynamique pour l'inférence sur CPU
quantized_model = quantize_dynamic(
model,
{torch.nn.Linear},
dtype=torch.qint8
)
Conversion en ONNX : Exporter vers ONNX pour une inférence optimisée :
import torch.onnx
dummy_input = processor(text=["exemple"], return_tensors="pt")
torch.onnx.export(
model,
tuple(dummy_input.values()),
"clip_model.onnx",
input_names=['input_ids', 'attention_mask'],
output_names=['output'],
dynamic_axes={
'input_ids': {0: 'batch_size'},
'attention_mask': {0: 'batch_size'}
}
)
Traitement par lots : Maximiser l’utilisation du GPU via le traitement par lots :
def batch_encode(items: List, batch_size: int = 32):
"""Processer les éléments par lots pour l'efficacité"""
embeddings = []
for i in range(0, len(items), batch_size):
batch = items[i:i+batch_size]
batch_embeddings = encode_batch(batch)
embeddings.append(batch_embeddings)
return np.concatenate(embeddings, axis=0)
Stockage d’embbedings vectoriels à grande échelle
Pour les applications à grande échelle, les bases de données vectorielles sont essentielles. Quels cadres supportent les implémentations d’embeddings crois-modaux ? Au-delà des modèles eux-mêmes, l’infrastructure compte :
FAISS (Facebook AI Similarity Search) : Bibliothèque efficace de recherche de similarité
- Supporte des milliards de vecteurs
- Plusieurs types d’index (plat, IVF, HNSW)
- Accélération GPU disponible
Milvus : Base de données vectorielle open source
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType
# Définir le schéma
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=512),
FieldSchema(name="metadata", dtype=DataType.JSON)
]
schema = CollectionSchema(fields, description="Embeddings d'images")
# Créer la collection
collection = Collection("images", schema)
# Créer l'index
index_params = {
"metric_type": "IP", # Produit scalaire (similarité cosinus)
"index_type": "IVF_FLAT",
"params": {"nlist": 1024}
}
collection.create_index("embedding", index_params)
Pinecone/Weaviate : Services de base de données vectorielle gérés offrant un échelonnage et une maintenance faciles.
Cas d’utilisation avancés et applications
Classification à zéro exemple
Les embeddings crois-modaux permettent la classification sans entraînement spécifique à la tâche. Cette capacité s’étend au-delà des approches traditionnelles de vision par ordinateur – par exemple, si vous êtes intéressé par une détection d’objets plus spécialisée avec TensorFlow, cela représente une approche d’apprentissage supervisé complémentaire pour des tâches spécifiques de détection.
def zero_shot_classify(image, candidate_labels: List[str], model, processor):
"""Classer une image dans des catégories arbitraires"""
# Créer les prompts textuels
text_inputs = [f"une photo d'un {label}" for label in candidate_labels]
# Obtenir les embeddings
inputs = processor(
text=text_inputs,
images=image,
return_tensors="pt",
padding=True
)
outputs = model(**inputs)
# Calculer les probabilités
logits = outputs.logits_per_image
probs = logits.softmax(dim=1)
# Retourner les prédictions classées
sorted_indices = probs.argsort(descending=True)[0]
return [(candidate_labels[idx], probs[0][idx].item()) for idx in sorted_indices]
# Exemple d'utilisation
labels = ["chat", "chien", "oiseau", "poisson", "cheval"]
predictions = zero_shot_classify(image, labels, model, processor)
print(f"Meilleure prédiction : {predictions[0]}")
RAG multimodal (Retrieval-Augmented Generation)
Combiner les embeddings crois-modaux avec des modèles de langage crée des systèmes RAG multimodaux puissants. Une fois que vous avez récupéré des documents pertinents, le réordonnancement avec des modèles d’embeddings peut considérablement améliorer la qualité des résultats en réordonnant les candidats récupérés en fonction de leur pertinence :
class MultimodalRAG:
def __init__(self, clip_model, llm_model):
self.clip = clip_model
self.llm = llm_model
self.knowledge_base = []
def add_documents(self, images, texts, metadata):
"""Ajouter des documents multimodaux à la base de connaissances"""
image_embeds = self.clip.encode_images(images)
text_embeds = self.clip.encode_text(texts)
# Stocker les informations combinées
for i, (img_emb, txt_emb, meta) in enumerate(
zip(image_embeds, text_embeds, metadata)
):
self.knowledge_base.append({
'image_embedding': img_emb,
'text_embedding': txt_emb,
'metadata': meta,
'index': i
})
def retrieve(self, query: str, k: int = 5):
"""Récupérer du contenu multimodal pertinent"""
query_embedding = self.clip.encode_text([query])[0]
# Calculer les similarités
similarities = []
for doc in self.knowledge_base:
# Moyenne de similarité à travers les modalités
img_sim = np.dot(query_embedding, doc['image_embedding'])
txt_sim = np.dot(query_embedding, doc['text_embedding'])
combined_sim = (img_sim + txt_sim) / 2
similarities.append((combined_sim, doc))
# Retourner les k premiers
similarities.sort(reverse=True)
return [doc for _, doc in similarities[:k]]
def answer_query(self, query: str):
"""Répondre à une question à l'aide du contexte multimodal récupéré"""
retrieved_docs = self.retrieve(query)
# Construire le contexte à partir des documents récupérés
context = "\n".join([doc['metadata']['text'] for doc in retrieved_docs])
# Générer une réponse avec le LLM
prompt = f"Contexte:\n{context}\n\nQuestion: {query}\n\nRéponse:"
answer = self.llm.generate(prompt)
return answer, retrieved_docs
Si vous implémentez des systèmes RAG en production en Go, vous trouverez ce guide sur le réordonnancement de documents textuels avec Ollama et le modèle Qwen3 Embedding en Go particulièrement utile pour optimiser la qualité de la récupération.
Modération de contenu et sécurité
Les embeddings crois-modaux excellent dans la détection de contenu inapproprié à travers les modalités :
class ContentModerator:
def __init__(self, model, processor):
self.model = model
self.processor = processor
# Définir les catégories de sécurité
self.unsafe_categories = [
"contenu violent",
"contenu adulte",
"imagery haineuse",
"violence graphique",
"matériel explicite"
]
self.safe_categories = [
"sécurisé pour le travail",
"familial",
"contenu éducatif"
]
def moderate_image(self, image, threshold: float = 0.3):
"""Vérifier si l'image contient du contenu non sécurisé"""
# Combiner toutes les catégories
all_categories = self.unsafe_categories + self.safe_categories
text_inputs = [f"image contenant {cat}" for cat in all_categories]
# Obtenir les prédictions
inputs = self.processor(
text=text_inputs,
images=image,
return_tensors="pt"
)
outputs = self.model(**inputs)
probs = outputs.logits_per_image.softmax(dim=1)[0]
# Vérifier les catégories non sécurisées
unsafe_scores = probs[:len(self.unsafe_categories)]
max_unsafe_score = unsafe_scores.max().item()
return {
'is_safe': max_unsafe_score < threshold,
'confidence': 1 - max_unsafe_score,
'flagged_categories': [
self.unsafe_categories[i]
for i, score in enumerate(unsafe_scores)
if score > threshold
]
}
Bonnes pratiques et erreurs courantes
Prétraitement des données
Un prétraitement correct est crucial pour une performance optimale :
from torchvision import transforms
# Prétraitement standard CLIP
clip_transform = transforms.Compose([
transforms.Resize(224, interpolation=transforms.InterpolationMode.BICUBIC),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.48145466, 0.4578275, 0.40821073],
std=[0.26862954, 0.26130258, 0.27577711]
)
])
Gestion des biais et de l’équité
Les modèles crois-modaux peuvent hériter de biais des données d’entraînement :
Stratégies de mitigation :
- Évaluer sur des groupes démographiques divers
- Utiliser des techniques de débaisement pendant l’ajustement fin
- Implémenter une récupération consciente de l’équité
- Audit et surveillance réguliers en production
Évaluation de la qualité des embeddings
Surveiller la qualité des embeddings en production :
def assess_embedding_quality(embeddings: np.ndarray):
"""Calculer des métriques pour la qualité des embeddings"""
# Calculer la distance moyenne paire
distances = np.linalg.norm(
embeddings[:, None] - embeddings[None, :],
axis=2
)
avg_distance = distances.mean()
# Vérifier le regroupement (distance intra-classe faible)
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=10)
labels = kmeans.fit_predict(embeddings)
# Calculer le score de silhouette
from sklearn.metrics import silhouette_score
score = silhouette_score(embeddings, labels)
return {
'avg_pairwise_distance': avg_distance,
'silhouette_score': score
}
Exemple de déploiement Docker
Empaqueter votre application crois-modale pour un déploiement facile :
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
# Installer Python et les dépendances
RUN apt-get update && apt-get install -y python3-pip
WORKDIR /app
# Installer les exigences
COPY requirements.txt .
RUN pip3 install --no-cache-dir -r requirements.txt
# Copier le code de l'application
COPY . .
# Exposer le port API
EXPOSE 8000
# Lancer le serveur API
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000"]
# api.py - Serveur FastAPI pour les embeddings crois-modaux
from fastapi import FastAPI, File, UploadFile
from pydantic import BaseModel
import torch
from PIL import Image
import io
app = FastAPI()
# Charger le modèle au démarrage
@app.on_event("startup")
async def load_model():
global model, processor
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
if torch.cuda.is_available():
model = model.cuda()
class TextQuery(BaseModel):
text: str
@app.post("/embed/text")
async def embed_text(query: TextQuery):
inputs = processor(text=[query.text], return_tensors="pt", padding=True)
if torch.cuda.is_available():
inputs = {k: v.cuda() for k, v in inputs.items()}
with torch.no_grad():
embeddings = model.get_text_features(**inputs)
return {"embedding": embeddings.cpu().numpy().tolist()}
@app.post("/embed/image")
async def embed_image(file: UploadFile = File(...)):
image = Image.open(io.BytesIO(await file.read()))
inputs = processor(images=image, return_tensors="pt")
if torch.cuda.is_available():
inputs = {k: v.cuda() for k, v in inputs.items()}
with torch.no_grad():
embeddings = model.get_image_features(**inputs)
return {"embedding": embeddings.cpu().numpy().tolist()}
@app.post("/similarité")
async def compute_similarity(
text: TextQuery,
file: UploadFile = File(...)
):
# Obtenir les deux embeddings
image = Image.open(io.BytesIO(await file.read()))
inputs = processor(text=[text.text], images=image, return_tensors="pt", padding=True)
if torch.cuda.is_available():
inputs = {k: v.cuda() for k, v in inputs.items()}
outputs = model(**inputs)
similarity = outputs.logits_per_image[0][0].item()
return {"similarité": similarity}
Perspectives futures
Le domaine des embeddings crois-modaux évolue rapidement :
Couverture modale plus large : Les modèles futurs incorporeront probablement d’autres modalités comme le toucher (retour haptique), l’odeur et le goût pour une compréhension multimodale complète.
Meilleure efficacité : La recherche sur la distillation et les architectures efficaces rendra les modèles crois-modaux puissants accessibles sur les appareils de périphérie.
Meilleure alignement : Des techniques avancées pour aligner plus précisément les modalités, y compris les pertes de cohérence cyclique et l’entraînement adversarial.
Compréhension compositionnelle : Passer au-delà de la reconnaissance d’objets simples pour comprendre des relations et des compositions complexes à travers les modalités.
Modélisation temporelle : Une meilleure gestion des données vidéo et des séries temporelles avec une raison temporelle explicite dans les espaces d’embeddings.
Liens utiles
- Référentiel CLIP d’OpenAI
- OpenCLIP : Implémentation open source
- Documentation des Transformers de Hugging Face
- ImageBind de Meta
- Dataset LAION-5B
- Documentation FAISS
- Base de données vectorielle Milvus
- Base de données vectorielle Pinecone
- Papier : Apprendre des modèles visuels transférables à partir de la supervision naturelle du langage
- Papier : ImageBind : Un espace d’embedding pour les unir tous
- Détection d’objets avec TensorFlow
- Réordonnancement avec des modèles d’embeddings
- Réordonnancement de documents textuels avec Ollama et le modèle Qwen3 Embedding - en Go
- Modèles Qwen3 Embedding & Reranker sur Ollama : Performances d’avant-garde
Les embeddings crois-modaux représentent un changement de paradigme dans la manière dont les systèmes d’IA traitent et comprennent l’information. En éliminant les barrières entre différents types de données, ces techniques permettent des applications d’IA plus naturelles et plus capables. Que vous construisez des systèmes de recherche, des outils de modération de contenu ou des applications créatives, maîtriser les embeddings crois-modaux ouvre la voie à un monde d’opportunités d’innovation en IA multimodale.