Multimodalité#

Les chapitres précédents ont traité les LLM comme des machines textuelles : ils reçoivent du texte, produisent du texte, raisonnent sur du texte. Or les humains sont fondamentalement multimodaux — nous percevons le monde simultanément par la vision, l’audition, le toucher et le langage, et nous intégrons ces flux d’information en permanence pour comprendre et agir. Un médecin qui lit une radiographie combine l’image et le texte du rapport clinique ; un étudiant apprend d’un cours en combinant la parole, les diapositives et les notes écrites.

Depuis 2021, une serie d’avancées majeures a permis d’étendre les modèles de langage au-delà du texte, vers des architectures capables de traiter et de générer des images, de l’audio, de la vidéo et d’autres modalités. Cette évolution, amorcée par CLIP et DALL-E, a transformé le paysage de l’IA : les modèles les plus avances aujourd’hui — GPT-4o, Claude 3.5 Sonnet, Gemini 1.5 — sont nativement multimodaux.

Ce chapitre présente les fondations de la multimodalité dans le contexte des LLM. Nous étudierons CLIP et l’apprentissage contrastif texte-image, les architectures vision-langage (LLaVA, Flamingo, GPT-4V), les modèles audio (Whisper), les architectures unifiées et les principales applications multimodales. Nous ne chargeons aucun modèle multimodal complet (trop volumineux pour 8 Go de RAM), mais illustrons chaque concept par des calculs exécutables et des visualisations.

Vers des modèles multimodaux#

L’extension des modèles de langage à d’autres modalités répond à une double motivation. D’une part, de nombreuses tâches réelles ne peuvent pas être résolues par le texte seul : comprendre une photographie, transcrire de la parole, analyser un document PDF contenant des tableaux et des figures. D’autre part, l’intégration de plusieurs modalités permet un apprentissage plus riche — un modèle qui voit une image de chat accompagnée du mot « chat » apprend une représentation plus robuste que s’il ne voyait que l’image ou que le mot.

Définition 115 (Modèle multimodal)

Un modèle multimodal est un modèle d’apprentissage profond capable de traiter et/ou de générer des données provenant de plusieurs modalités (texte, image, audio, video, etc.) au sein d’une même architecture. Formellement, étant donné des entrées provenant de \(K\) modalités \(\{x^{(1)}, x^{(2)}, \ldots, x^{(K)}\}\), le modèle apprend une fonction \(f : \mathcal{X}^{(1)} \times \cdots \times \mathcal{X}^{(K)} \to \mathcal{Y}\) qui intègre l’information de toutes les modalités pour produire une sortie \(y\) dans un espace cible. L’enjeu central est l”alignement des représentations entre modalités : comment garantir qu’une image de chat et le texte « un chat assis sur un canapé » soient proches dans un espace de représentation commun ?

Les principales applications multimodales se regroupent en quatre catégories : la compréhension image-texte (image captioning, VQA, classification zero-shot), la compréhension de documents (factures, articles scientifiques, rapports médicaux), la parole et l’audio (ASR, traduction vocale), et la génération multimodale (DALL-E, Midjourney, Stable Diffusion).

Remarque 111

L’un des apports majeurs des modèles multimodaux est le transfert zero-shot entre modalités. Un modèle comme CLIP, entrainé sur des paires (image, texte), peut classifier des images dans des catégories qu’il n’a jamais vues explicitement pendant l’entrainement, simplement en comparant l’embedding de l’image avec les embeddings des descriptions textuelles des catégories. Ce paradigme élimine le besoin de datasets annotés spécifiques à chaque tâche, ce qui constitue un changement radical par rapport aux approches supervisées classiques.

CLIP : apprentissage contrastif texte-image#

Le modèle CLIP (Contrastive Language-Image Pre-training, Radford et al., 2021) est un point tournant dans l’histoire des modèles multimodaux. Son principe est élégant : entrainer conjointement un encodeur d’images et un encodeur de texte pour que les paires (image, légende) correspondantes soient proches dans un espace de représentation commun, tandis que les paires non correspondantes sont éloignées.

Définition 116 (CLIP (Contrastive Language-Image Pre-training))

CLIP est un modèle multimodal composé de deux encodeurs entrainés conjointement :

  1. Encodeur d’images \(f_I : \mathcal{I} \to \mathbb{R}^d\) — un Vision Transformer (ViT) ou un ResNet qui transforme une image en un vecteur d’embedding de dimension \(d\).

  2. Encodeur de texte \(f_T : \mathcal{T} \to \mathbb{R}^d\) — un Transformer textuel qui transforme une légende en un vecteur d’embedding de même dimension \(d\).

Les deux encodeurs projettent leurs sorties dans un espace de représentation commun de dimension \(d\) (typiquement \(d = 512\) ou \(d = 768\)). L’entrainement utilise un corpus de 400 millions de paires (image, légende) collectées sur le web.

Définition 117 (Perte contrastive (InfoNCE))

La perte contrastive de CLIP, aussi appelée InfoNCE loss, est définie pour un batch de \(N\) paires (image, texte) comme :

\[\mathcal{L} = -\frac{1}{2N}\sum_{i=1}^{N}\left[\log\frac{\exp(\text{sim}(I_i, T_i)/\tau)}{\sum_{j=1}^{N}\exp(\text{sim}(I_i, T_j)/\tau)} + \log\frac{\exp(\text{sim}(T_i, I_i)/\tau)}{\sum_{j=1}^{N}\exp(\text{sim}(T_i, I_j)/\tau)}\right]\]

\(I_i = f_I(\text{image}_i)\) et \(T_i = f_T(\text{texte}_i)\) sont les embeddings normalisés, \(\text{sim}(a, b) = a^\top b / (\|a\| \cdot \|b\|)\) est la similarité cosinus, et \(\tau > 0\) est un paramètre de température appris. Le premier terme est la perte image-vers-texte et le second est la perte texte-vers-image.

Définition 118 (Encodeur visuel (Vision Encoder))

Un encodeur visuel (vision encoder) est un réseau de neurones qui transforme une image brute \(x \in \mathbb{R}^{H \times W \times 3}\) en une représentation vectorielle \(\mathbf{h} \in \mathbb{R}^d\). Dans le contexte de CLIP, l’encodeur visuel est généralement un Vision Transformer (ViT) qui découpe l’image en patchs de taille \(p \times p\) (typiquement \(14 \times 14\) ou \(16 \times 16\)), traite chaque patch comme un « token visuel » et applique un Transformer standard. Pour une image de \(224 \times 224\) pixels avec des patchs de \(16 \times 16\), on obtient \((224/16)^2 = 196\) tokens visuels, auxquels on ajoute un token [CLS] dont la représentation finale sert d’embedding global de l’image.

Définition 119 (Espace de projection commun)

L”espace de projection commun (joint embedding space) est un espace vectoriel \(\mathbb{R}^d\) dans lequel les représentations d’images et de textes cohabitent. Chaque encodeur possède une couche de projection linéaire :

\[I_i = W_I \, \mathbf{h}_I^{(\text{CLS})} + b_I, \quad T_i = W_T \, \mathbf{h}_T^{(\text{CLS})} + b_T\]

\(W_I \in \mathbb{R}^{d \times d_I}\), \(W_T \in \mathbb{R}^{d \times d_T}\) sont les matrices de projection. Apres projection, les embeddings sont normalisés (\(\|I_i\| = \|T_i\| = 1\)) de sorte que la similarité cosinus se réduit à un simple produit scalaire.

L’intuition derrière la perte contrastive est simple : dans un batch de \(N\) paires, chaque image doit être associée à son texte et à aucun autre. La matrice de similarité \(S \in \mathbb{R}^{N \times N}\), ou \(S_{ij} = \text{sim}(I_i, T_j)\), doit idéalement être une matrice diagonale.

Hide code cell source

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
import torch
import torch.nn.functional as F

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
torch.manual_seed(42)
np.random.seed(42)

Hide code cell source

# Simulation de la perte contrastive CLIP sur de petits tenseurs
N = 8   # taille du batch
d = 64  # dimension de l'espace commun
tau = 0.07  # température

image_emb = F.normalize(torch.randn(N, d), dim=1)
text_emb = F.normalize(torch.randn(N, d), dim=1)

# Rendre les paires correspondantes proches (modèle partiellement entrainé)
for i in range(N):
    text_emb[i] = F.normalize(image_emb[i] + 0.3 * torch.randn(d), dim=0)

sim_matrix = image_emb @ text_emb.T  # (N, N)

logits_per_image = sim_matrix / tau
logits_per_text = sim_matrix.T / tau
labels = torch.arange(N)

loss_i2t = F.cross_entropy(logits_per_image, labels)
loss_t2i = F.cross_entropy(logits_per_text, labels)
loss_total = (loss_i2t + loss_t2i) / 2

print(f"Taille du batch : {N}")
print(f"Température : {tau}")
print(f"Perte image -> texte : {loss_i2t.item():.4f}")
print(f"Perte texte -> image : {loss_t2i.item():.4f}")
print(f"Perte totale (InfoNCE) : {loss_total.item():.4f}")
print(f"Perte théorique (batch parfaitement aligné) : {np.log(N):.4f}")
Taille du batch : 8
Température : 0.07
Perte image -> texte : 0.2782
Perte texte -> image : 0.1624
Perte totale (InfoNCE) : 0.2203
Perte théorique (batch parfaitement aligné) : 2.0794

Hide code cell source

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

sim_np = sim_matrix.detach().numpy()
labs = lambda n: ([f"T{i}" for i in range(n)], [f"I{i}" for i in range(n)])
sns.heatmap(sim_np, annot=True, fmt=".2f", cmap="YlOrRd",
            xticklabels=labs(N)[0], yticklabels=labs(N)[1],
            ax=axes[0], linewidths=0.5, square=True, vmin=-0.2, vmax=1.0,
            annot_kws={"fontsize": 9})
axes[0].set_xlabel("Textes"); axes[0].set_ylabel("Images")
axes[0].set_title("Matrice de similarité (modèle partiel)")

sns.heatmap(np.eye(N), annot=True, fmt=".0f", cmap="YlOrRd",
            xticklabels=labs(N)[0], yticklabels=labs(N)[1],
            ax=axes[1], linewidths=0.5, square=True, vmin=-0.2, vmax=1.0,
            annot_kws={"fontsize": 9})
axes[1].set_xlabel("Textes"); axes[1].set_ylabel("Images")
axes[1].set_title("Matrice idéale (objectif)")

fig.suptitle("Apprentissage contrastif CLIP : matrice de similarité $N \\times N$",
             fontsize=13, y=1.02)
plt.show()
_images/264d81cd32b1e2cb3e8611cffe0153a0c7ba7aa925b42406140827ea04039d03.png

Hide code cell source

# Architecture CLIP : double encodeur avec espace de projection
fig, ax = plt.subplots(figsize=(13, 6))
ax.set_xlim(0, 14); ax.set_ylim(0, 9); ax.axis('off')

def _box(x, y, w, h, label, color, sub=None):
    ax.add_patch(mpatches.FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.15",
                 facecolor=color, alpha=0.22, edgecolor=color, linewidth=2))
    ax.text(x + w/2, y + h*0.65, label, fontsize=10, ha="center", fontweight="bold", color=color)
    if sub: ax.text(x + w/2, y + h*0.25, sub, fontsize=8, ha="center", color="#333")

_box(0.5, 0.3, 2.5, 1.5, "Image", "#4C72B0")
_box(11, 0.3, 2.5, 1.5, "Texte", "#DD8452")
_box(0.5, 3.5, 2.5, 2.5, "Enc. images", "#4C72B0", "ViT-L/14")
_box(11, 3.5, 2.5, 2.5, "Enc. texte", "#DD8452", "Transformer")
_box(5, 3, 4, 2.5, "Espace commun\n$\\mathbb{R}^d$", "#55A868", "$\\mathrm{sim}(I_i, T_j)$")
_box(5, 7, 4, 1.5, "Perte InfoNCE", "#C44E52")

for x1, y1, x2, y2, c in [(1.75,1.8,1.75,3.5,"#4C72B0"), (12.25,1.8,12.25,3.5,"#DD8452"),
                            (3,4.75,5,4.25,"#4C72B0"), (11,4.75,9,4.25,"#DD8452"),
                            (7,5.5,7,7,"#C44E52")]:
    ax.annotate('', xy=(x2,y2), xytext=(x1,y1),
                arrowprops=dict(arrowstyle='->', color=c, lw=2))
ax.text(5.7, 3.5, "$I_i$", fontsize=12, color="#4C72B0", fontweight="bold")
ax.text(8.3, 3.5, "$T_i$", fontsize=12, color="#DD8452", fontweight="bold")
ax.set_title("Architecture CLIP : double encodeur avec espace de projection commun", fontsize=13, pad=12)
plt.show()
_images/e872ce6302631c1b636fb1cfb598f932394d2587246ed7cc53aa20374a3ee5e0.png

Propriété 27 (Lois d’echelle de CLIP)

Radford et al. (2021) ont montré que les performances zero-shot de CLIP suivent des lois d’échelle similaires à celles des LLM textuels :

  1. Echelle du modèle : augmenter la taille de l’encodeur visuel (de ViT-B/32 a ViT-L/14) améliore les performances sur ImageNet zero-shot (de 63 % a 76 %).

  2. Echelle des données : passer de 15 millions a 400 millions de paires (image, texte) apporte des gains continus.

  3. Qualité des données : la qualité et la diversité des légendes sont aussi importantes que la quantité. Le corpus WIT (WebImageText) de 400M de paires est un facteur clé du succès de CLIP.

Modèles vision-language#

CLIP est un modèle d”alignement : il apprend à mettre en correspondance images et textes dans un espace commun, mais ne génère pas de texte. Les modèles vision-language (Vision-Language Models, VLM) vont plus loin en combinant un encodeur visuel avec un LLM génératif, permettant de répondre à des questions sur des images, de dŕcrire des scènes et de raisonner visuellement.

Exemple 78 (Architectures vision-language)

Modèle

Architecture

Intégration visuelle

Année

Flamingo

NFNet/ViT + Chinchilla

Cross-attention (Perceiver Resampler)

2022

LLaVA

ViT (CLIP) + LLaMA/Vicuna

Projection linéaire des tokens visuels

2023

GPT-4V

Encodeur visuel + GPT-4

Non publié (probablement cross-attention)

2023

Claude 3/3.5

Encodeur visuel + Claude

Non publié

2024

Gemini

Encodeur multimodal natif

Tokenisation native image/audio/video

2023

Deux grandes strategies se dégagent :

  1. Adapter-based (LLaVA, Flamingo) : un encodeur visuel pré-entrainé (souvent CLIP ViT) est connecté à un LLM via une couche de projection ou un module d’attention croisée.

  2. Native multimodal (Gemini) : le modèle est entrainé dès le départ sur des données multimodales, sans séparation nette entre encodeur visuel et LLM.

Remarque 112

L’approche Visual Instruction Tuning (Liu et al., 2023), qui sous-tend LLaVA, consiste à générer des données d’entrainement en utilisant GPT-4 pour produire des paires (image + question, réponse détaillée) à partir de légendes et de boites englobantes. LLaVA-1.5 atteint des performances compétitives avec seulement 600k exemples d’instruction tuning, contre des milliards de paires pour CLIP. L’insight clé est que la majeure partie de la compréhension visuelle est déjà encodée dans le ViT pré-entrainé ; il suffit d’apprendre à « traduire » les tokens visuels dans le langage du LLM.

Hide code cell source

fig, axes = plt.subplots(1, 3, figsize=(16, 6))

def _vbox(ax, x, y, w, h, label, color, sub=None):
    ax.add_patch(mpatches.FancyBboxPatch((x,y), w, h, boxstyle="round,pad=0.1",
                 facecolor=color, alpha=0.22, edgecolor=color, linewidth=1.8))
    ax.text(x+w/2, y+h*0.65, label, fontsize=8, ha="center", fontweight="bold", color=color)
    if sub: ax.text(x+w/2, y+h*0.25, sub, fontsize=6.5, ha="center", color="#333")

def _varr(ax, x1, y1, x2, y2, c='gray'):
    ax.annotate('', xy=(x2,y2), xytext=(x1,y1), arrowprops=dict(arrowstyle='->', color=c, lw=1.3))

# LLaVA
ax = axes[0]; ax.set_xlim(0,6); ax.set_ylim(0,10); ax.axis('off')
ax.set_title("LLaVA (projection)", fontsize=10, fontweight="bold")
_vbox(ax,0.3,0.3,2.4,1.3,"Image","#4C72B0"); _vbox(ax,3.3,0.3,2.4,1.3,"Texte","#DD8452")
_vbox(ax,0.3,3,2.4,1.8,"ViT (CLIP)","#4C72B0","gele"); _vbox(ax,1.2,6,3.6,1,"Proj. linéaire","#55A868")
_vbox(ax,0.5,8,5,1.5,"LLM (LLaMA)","#C44E52","fine-tune")
_varr(ax,1.5,1.6,1.5,3); _varr(ax,4.5,1.6,4.5,8,"#DD8452"); _varr(ax,1.5,4.8,2.5,6); _varr(ax,3,7,3,8)

# Flamingo
ax = axes[1]; ax.set_xlim(0,6); ax.set_ylim(0,10); ax.axis('off')
ax.set_title("Flamingo (cross-attn)", fontsize=10, fontweight="bold")
_vbox(ax,0.3,0.3,2.4,1.3,"Image(s)","#4C72B0"); _vbox(ax,3.3,0.3,2.4,1.3,"Texte","#DD8452")
_vbox(ax,0.3,3,2.4,1.8,"ViT+Perceiver","#4C72B0","Resampler"); _vbox(ax,0.5,6,5,1,"Cross-attn","#55A868")
_vbox(ax,0.5,8,5,1.5,"LLM (gele)","#C44E52")
_varr(ax,1.5,1.6,1.5,3); _varr(ax,4.5,1.6,4.5,6,"#DD8452"); _varr(ax,1.5,4.8,2.5,6); _varr(ax,3,7,3,8)

# Gemini
ax = axes[2]; ax.set_xlim(0,6); ax.set_ylim(0,10); ax.axis('off')
ax.set_title("Gemini (natif)", fontsize=10, fontweight="bold")
_vbox(ax,0.2,0.3,1.6,1.3,"Image","#4C72B0"); _vbox(ax,2.1,0.3,1.6,1.3,"Audio","#8B6DAF")
_vbox(ax,4,0.3,1.6,1.3,"Texte","#DD8452")
_vbox(ax,0.2,3,5.4,1.3,"Tokeniseurs multimodaux","#55A868","patchs / frames / BPE")
_vbox(ax,0.3,5.5,5.2,4,"Transformer unifié","#C44E52","entraine de bout en bout")
_varr(ax,1,1.6,1.5,3); _varr(ax,2.9,1.6,2.9,3); _varr(ax,4.8,1.6,4.3,3); _varr(ax,2.9,4.3,2.9,5.5)

fig.suptitle("Comparaison des architectures vision-language", fontsize=13, y=1.0)
plt.show()
_images/d367b6817853789211ed3858f7e42a02d0a98c8492eeebba7ffb14a1662bd23f.png

Remarque 113

Les modèles vision-language souffrent d”hallucinations visuelles : ils peuvent affirmer avec confiance la présence d’objets absents de l’image, inventer des détails ou ignorer des éléments visibles. Les benchmarks d’hallucination visuelle (POPE, CHAIR) montrent que même les meilleurs modèles (GPT-4V, Claude 3.5) hallucinent dans 5 à 15 % des cas. Les causes incluent les biais du LLM génératif (qui « invente » par défaut), le manque de résolution de l’encodeur visuel et les données d’entrainement bruitées.

Modèles audio et speech#

La parole est la modalité de communication la plus naturelle pour l’être humain. L’intégration de la compréhension audio dans les modèles multimodaux permet la transcription automatique, la traduction vocale et l’intéraction vocale directe avec les LLM.

Définition 120 (Whisper (modèle de reconnaissance vocale))

Whisper (Radford et al., 2022) est un modèle de reconnaissance automatique de la parole (ASR) développé par OpenAI, fondé sur une architecture encodeur-decodeur Transformer :

  1. Encodeur audio : transforme un spectrogramme log-Mel de 30 secondes (80 bandes \(\times\) ~3000 frames) en représentations contextuelles.

  2. Décodeur textuel : génère la transcription de manière auto-régressive.

  3. Multitâche : transcription, traduction, détection de langue et détection d’activité vocale, via des tokens spéciaux.

  4. Données : 680 000 heures d’audio supervisé, 96 langues.

  5. Tailles : de 39M (Tiny) a 1,5B (Large-v3) paramètres.

Exemple 79 (Utilisation de Whisper)

import whisper

model = whisper.load_model("base")  # ~140 Mo
result = model.transcribe("audio.mp3")

print(result["text"])
print(result["language"])

for segment in result["segments"]:
    print(f"[{segment['start']:.1f}s - {segment['end']:.1f}s] {segment['text']}")

Whisper base (74M paramètres, ~140 Mo) tourne sur CPU. Les modèles large-v3 (1,5B) offrent une qualité quasi humaine mais nécessitent un GPU.

Remarque 114

L’approche de Whisper est fondamentalement différente des systèmes ASR précédents (DeepSpeech, Wav2Vec 2.0) qui utilisaient des architectures encodeur seul avec un décodage CTC (Connectionist Temporal Classification). En adoptant l’architecture encodeur-décodeur et en entrainant sur des données massives et diversifiées, Whisper atteint une robustesse remarquable face au bruit, aux accents et aux conditions d’enregistrement variées. Les modèles recents (GPT-4o, Gemini 1.5) integrent nativement la compréhension audio, étendant les capacités de Whisper au raisonnement et à la compréhension du ton et des émotions.

Hide code cell source

# Architecture Whisper (encodeur-décodeur)
fig, ax = plt.subplots(figsize=(13, 5))
ax.set_xlim(0, 14); ax.set_ylim(0, 7); ax.axis('off')

def _wbox(x, y, w, h, label, color, sub=None):
    ax.add_patch(mpatches.FancyBboxPatch((x,y), w, h, boxstyle="round,pad=0.15",
                 facecolor=color, alpha=0.22, edgecolor=color, linewidth=2))
    ax.text(x+w/2, y+h*0.7, label, fontsize=10, ha="center", fontweight="bold", color=color)
    if sub: ax.text(x+w/2, y+h*0.25, sub, fontsize=7.5, ha="center", color="#333")

_wbox(0.3, 0.3, 3, 1.5, "Audio (30s)", "#8B6DAF", "Spectrogramme log-Mel")
_wbox(0.3, 3, 3, 3, "Encodeur", "#4C72B0", "Conv1D + self-attention")
_wbox(5.5, 3, 3, 3, "Décodeur", "#DD8452", "Masked self-attn\n+ cross-attention")
_wbox(10.5, 3, 3, 3, "Sortie", "#55A868", "<|lang|> <|task|>\ntranscription tokens")

for x1,y1,x2,y2,c in [(1.8,1.8,1.8,3,"#8B6DAF"), (3.3,4.5,5.5,4.5,"#4C72B0"), (8.5,4.5,10.5,4.5,"#DD8452")]:
    ax.annotate('', xy=(x2,y2), xytext=(x1,y1), arrowprops=dict(arrowstyle='->', color=c, lw=2))
ax.text(4.4, 4.9, "cross-attn", fontsize=8, ha="center", color="#4C72B0", fontstyle="italic")
ax.set_title("Architecture Whisper : encodeur-décodeur Transformer pour la parole", fontsize=12, pad=10)
plt.show()
_images/bff95a4aa2d894c3705ed172c9cd375263fbdaf4cee99c6534e7c33ba40154cb.png

Architectures unifiées#

Les premières approches multimodales (CLIP, LLaVA) étaient modulaires : un encodeur spécialisé par modalité, connecté à un LLM par une couche de projection ou d’attention croisée. Les architectures récentes tendent vers des modèles unifiés capables de traiter plusieurs modalités nativement.

Remarque 115

La distinction entre architectures modulaires (adapter-based) et unifiées (native multimodal) a des conséquences profondes. L’approche modulaire permet de réutiliser des composants pré-entrainés (CLIP ViT, LLaMA) et de ne fine-tuner que la couche de connexion, à moindre coût. L’approche unifiée apprend dès le départ à intégrer toutes les modalités dans une représentation commune — l’alignement est plus profond mais l’entrainement est beaucoup plus coûteux. Gemini (Google, 2023) est le premier modèle à grande échelle à adopter cette approche nativement.

Les trois grandes stratégies de fusion entre modalités sont la fusion précoce (early fusion — tokenisation et concaténation de toutes les modalités avant le Transformer, approche de Gemini), la fusion tardive (late fusion — encodeurs indépendants combinés en sortie, approche de CLIP), et la fusion croisée (cross fusion — attention croisée à différents niveaux de profondeur, approche de Flamingo).

Remarque 116

Le choix entre fusion précoce, tardive et croisée implique un compromis fondamental. La fusion précoce permet les intéractions les plus riches entre modalités (chaque token visuel peut « voir » chaque token textuel des la première couche) mais requiert un entrainement multimodal de bout en bout extrêmement coûteux. La fusion tardive est la plus modulaire mais l’intéraction entre modalités est limitée à la couche finale. La fusion croisée offre un compromis intermédiaire, permettant une intéraction profonde tout en conservant des encodeurs spécialisés.

Hide code cell source

fig, axes = plt.subplots(1, 3, figsize=(16, 6))
titles = ["Fusion précoce\n(early)", "Fusion tardive\n(late)", "Fusion croisée\n(cross)"]

for ax, title in zip(axes, titles):
    ax.set_xlim(0, 6); ax.set_ylim(0, 10); ax.axis('off')
    ax.set_title(title, fontsize=10, fontweight="bold")

def _fb(ax, x, y, w, h, label, color, sub=None):
    ax.add_patch(mpatches.FancyBboxPatch((x,y), w, h, boxstyle="round,pad=0.08",
                 facecolor=color, alpha=0.22, edgecolor=color, linewidth=1.5))
    ax.text(x+w/2, y+h*0.65, label, fontsize=8, ha="center", fontweight="bold", color=color)
    if sub: ax.text(x+w/2, y+h*0.2, sub, fontsize=6.5, ha="center", color="#333")

def _fa(ax, x1,y1,x2,y2, c='gray'):
    ax.annotate('', xy=(x2,y2), xytext=(x1,y1), arrowprops=dict(arrowstyle='->', color=c, lw=1.2))

# Early
ax = axes[0]
_fb(ax,0.3,0.3,2.2,1.2,"Image","#4C72B0"); _fb(ax,3.5,0.3,2.2,1.2,"Texte","#DD8452")
_fb(ax,0.3,2.5,5.4,1.2,"Tokenisation conjointe","#55A868","patchs + BPE -> seq unique")
_fb(ax,0.3,5,5.4,4,"Transformer unifié","#C44E52","self-attention sur tous tokens")
_fa(ax,1.4,1.5,2,2.5); _fa(ax,4.6,1.5,4,2.5); _fa(ax,3,3.7,3,5)

# Late
ax = axes[1]
_fb(ax,0.3,0.3,2.2,1.2,"Image","#4C72B0"); _fb(ax,3.5,0.3,2.2,1.2,"Texte","#DD8452")
_fb(ax,0.3,2.5,2.2,3,"Enc. visuel","#4C72B0","ViT"); _fb(ax,3.5,2.5,2.2,3,"Enc. textuel","#DD8452","Transformer")
_fb(ax,0.3,7,5.4,2,"Combinaison","#55A868","cos sim / concat / MLP")
_fa(ax,1.4,1.5,1.4,2.5); _fa(ax,4.6,1.5,4.6,2.5); _fa(ax,1.4,5.5,2,7); _fa(ax,4.6,5.5,4,7)

# Cross
ax = axes[2]
_fb(ax,0.3,0.3,2.2,1.2,"Image","#4C72B0"); _fb(ax,3.5,0.3,2.2,1.2,"Texte","#DD8452")
_fb(ax,0.3,2.5,2.2,1.8,"Enc. visuel","#4C72B0")
_fb(ax,3.5,2.5,2.2,6.5,"LLM + cross-attn","#DD8452","self-attn / xattn")
_fa(ax,1.4,1.5,1.4,2.5); _fa(ax,4.6,1.5,4.6,2.5)
for dy in [0.5, 1.2, 1.9]:
    _fa(ax, 2.5, 3+dy, 3.5, 4+dy, "#4C72B0")

fig.suptitle("Stratégies de fusion multimodale", fontsize=12, y=1.0)
plt.show()
_images/38c07cd4eb36bbb209d5f22f450d27fdf3226fb4ed412be5cd2c57c31f22f72b.png

Hide code cell source

# Chronologie des modèles multimodaux (2021--2025)
models_timeline = [
    ("CLIP", 2021.0, "Alignement"), ("DALL-E", 2021.0, "Génération"),
    ("Flamingo", 2022.3, "VLM"), ("Whisper", 2022.7, "Audio"),
    ("Stable Diffusion", 2022.6, "Génération"), ("GPT-4V", 2023.2, "VLM"),
    ("LLaVA", 2023.3, "VLM"), ("Midjourney V5", 2023.3, "Génération"),
    ("DALL-E 3", 2023.8, "Génération"), ("Gemini 1.0", 2023.9, "Unifié"),
    ("Claude 3", 2024.2, "VLM"), ("Gemini 1.5", 2024.2, "Unifié"),
    ("Sora", 2024.2, "Génération"), ("GPT-4o", 2024.4, "Unifié"),
    ("Claude 3.5", 2024.5, "VLM"),
]

cat_map = {"Alignement": 0, "VLM": 1, "Audio": 2, "Génération": 3, "Unifié": 4}
cat_colors = {"Alignement": "#4C72B0", "VLM": "#DD8452", "Audio": "#8B6DAF",
              "Génération": "#55A868", "Unifié": "#C44E52"}

fig, ax = plt.subplots(figsize=(14, 5))
for name, year, cat in models_timeline:
    color = cat_colors[cat]
    ax.scatter(year, cat_map[cat], s=130, color=color, zorder=5, edgecolors="white", linewidth=0.8)
    offset = 11 if hash(name) % 2 == 0 else -13
    ax.annotate(name, (year, cat_map[cat]), fontsize=7.5, ha="center",
                xytext=(0, offset), textcoords="offset points", color=color, fontweight="bold")

ax.set_yticks(list(cat_map.values())); ax.set_yticklabels(list(cat_map.keys()), fontsize=10)
ax.set_xlabel("Année"); ax.set_xlim(2020.5, 2025)
ax.set_title("Chronologie des modèles multimodaux (2021--2025)", fontsize=13)
ax.legend(handles=[mpatches.Patch(color=c, label=k) for k, c in cat_colors.items()],
          fontsize=8, loc="upper left")
plt.show()
_images/c3384be678260422028abaac5ea95e8892018f9f5838fe1f9be5dcfdf886b93f.png

Applications multimodales#

Les modèles multimodaux ont ouvert un large éventail d’applications qui étaient hors de portée des modèles textuels purs.

Remarque 117

La compréhension de documents (document understanding) est l’une des applications multimodales les plus immédiates en entreprise. Les documents réels — factures, contrats, articles scientifiques, rapports medicaux — mélangent texte, tableaux, figures et mise en page. Des modèles comme GPT-4V et Claude 3.5 Sonnet surpassent les pipelines OCR + NLP traditionnels grace à leur compréhension holistique du document. Le benchmark DocVQA mesure la capacité à répondre à des questions sur des documents scannés ; les meilleurs modèles dépassent 90 % de précision.

Remarque 118

L”analyse vidéo étend la compréhension d’image à la dimension temporelle. Les approches actuelles échantillonnent typiquement \(k\) frames par vidéo, les encodent séparément avec un ViT, puis fournissent la séquence de représentations visuelles au LLM. Gemini 1.5 Pro, avec sa fenêtre de contexte de 2 millions de tokens, peut traiter directement jusqu’à une heure de video. Les défis principaux sont le coût computationnel (une minute de video à 1 fps représente déjà 60 images, soit environ 12 000 tokens visuels) et la compréhension des relations temporelles entre frames.

Exemple 80 (Génération d’images à partir de texte)

La génération d’images à partir de texte a connu une révolution entre 2021 et 2023 :

Modèle

Approche

Année

DALL-E

dVAE + Transformer auto-régressif

2021

Stable Diffusion

Diffusion latente (U-Net + CLIP)

2022

Midjourney

Propriétaire (probablement diffusion)

2022

DALL-E 3

Diffusion + intégration ChatGPT

2023

Le paradigme dominant est la diffusion latente (Latent Diffusion Model, LDM) : un autoencodeur compresse l’image dans un espace latent compact, puis un modèle de diffusion apprend à débruiter progressivement un bruit gaussien pour générer une image cohérente, conditionné par des embeddings CLIP du prompt textuel.

Remarque 119

L”imagerie médicale est un domaine d’application particulièrement prometteur pour les modèles multimodaux. La combinaison d’images médicales (radiographies, IRM, scanners) et de rapports cliniques textuels permet d’assister les radiologues dans le diagnostic. Des modèles spécialisés (MedPaLM-M, LLaVA-Med) sont entrainés sur des jeux de données médicaux. Les enjeux specifiques incluent la fiabilité (les hallucinations visuelles peuvent avoir des conséquences graves), la confidentialité des données patients et les exigences réglementaires.

Hide code cell source

# Evolution simulée des performances multimodales sur différents benchmarks
benchmarks = {
    "VQAv2": [72, 78, 84, 88, 91], "TextVQA": [45, 55, 65, 75, 82],
    "DocVQA": [40, 55, 68, 80, 88], "MMMU": [25, 32, 42, 56, 68],
    "MathVista": [20, 28, 38, 50, 62],
}
annees = [2021, 2022, 2023, 2024, 2025]
bench_colors = {"VQAv2": "#4C72B0", "TextVQA": "#DD8452", "DocVQA": "#55A868",
                "MMMU": "#C44E52", "MathVista": "#8B6DAF"}

fig, ax = plt.subplots(figsize=(11, 5))
for bench, scores in benchmarks.items():
    ax.plot(annees, scores, 'o-', label=bench, color=bench_colors[bench], linewidth=2, markersize=5)
ax.set_xlabel("Année"); ax.set_ylabel("Score (%)")
ax.set_title("Progression sur les benchmarks multimodaux (données illustratives)")
ax.legend(fontsize=9); ax.set_ylim(0, 100)
ax.axhline(y=90, color='gray', linestyle='--', alpha=0.3)
ax.text(2025.05, 91, "seuil humain\n(approx.)", fontsize=8, color='gray')
plt.show()
_images/95caf0da66e25a7c1fabc8b5034f7c5b480f69c4d289946f5244db27431ffe2a.png

Hide code cell source

# Simulation : évolution de la perte contrastive pendant l'entrainement CLIP
epochs = np.arange(1, 51)
loss_i2t_curve = 4.0 * np.exp(-0.06 * epochs) + 0.8 + np.random.normal(0, 0.05, len(epochs))
loss_t2i_curve = 4.0 * np.exp(-0.055 * epochs) + 0.85 + np.random.normal(0, 0.05, len(epochs))
loss_tot_curve = (loss_i2t_curve + loss_t2i_curve) / 2

fig, axes = plt.subplots(1, 2, figsize=(13, 5))

axes[0].plot(epochs, loss_i2t_curve, '-', color='#4C72B0', linewidth=2,
             label='Image $\\to$ texte', alpha=0.8)
axes[0].plot(epochs, loss_t2i_curve, '-', color='#DD8452', linewidth=2,
             label='Texte $\\to$ image', alpha=0.8)
axes[0].plot(epochs, loss_tot_curve, '-', color='#C44E52', linewidth=2.5, label='Totale (InfoNCE)')
axes[0].axhline(y=np.log(N), color='gray', linestyle='--', alpha=0.4)
axes[0].text(40, np.log(N)+0.15, f'$\\ln(N) = {np.log(N):.2f}$', fontsize=9, color='gray')
axes[0].set_xlabel("Epoque"); axes[0].set_ylabel("Perte")
axes[0].set_title("Perte contrastive (simulation)"); axes[0].legend(fontsize=8)

top1_acc = 100 * (1 - np.exp(-0.04 * epochs)) * 0.76 + np.random.normal(0, 0.5, len(epochs))
axes[1].plot(epochs, top1_acc, 'o-', color='#55A868', linewidth=2, markersize=3)
axes[1].set_xlabel("Epoque"); axes[1].set_ylabel("Top-1 Accuracy (%)")
axes[1].set_title("Zero-shot ImageNet (simulation)"); axes[1].set_ylim(0, 85)
axes[1].axhline(y=76.2, color='gray', linestyle='--', alpha=0.4)
axes[1].text(35, 77.5, "CLIP ViT-L/14 : 76.2%", fontsize=9, color='gray')

fig.suptitle("Dynamique d'entrainement d'un modèle contrastif texte-image", fontsize=12, y=1.02)
plt.show()
_images/98701425decfbc4ce45c3c19545fc5f951ee700459bc45fa92872c042259ef3a.png

Résumé#

Ce chapitre a présenté les fondements de la multimodalité dans le contexte des grands modèles de langage, un domaine en évolution rapide qui redéfinit les capacités de l’IA.

  1. Les modèles multimodaux étendent les LLM au-delà du texte pour traiter images, audio et vidéo. Cette évolution répond au caractère fondamentalement multimodal de la perception humaine et ouvre des applications impossibles avec le texte seul (compréhension de documents, VQA, transcription vocale).

  2. CLIP (Radford et al., 2021) a introduit l’apprentissage contrastif texte-image à grande échelle. Son architecture à double encodeur et sa perte InfoNCE alignent images et textes dans un espace commun, permettant la classification zero-shot sans aucun entrainement spécifique à la tâche.

  3. La perte contrastive \(\mathcal{L}_{\text{InfoNCE}}\) maximise la similarité des paires correspondantes et minimise celle des paires non correspondantes dans un batch de \(N\) exemples. La température \(\tau\) contrôle la netteté de la distribution de similarité.

  4. Les modèles vision-language (LLaVA, Flamingo, GPT-4V, Claude) combinent un encodeur visuel (ViT) avec un LLM génératif. L’intégration se fait par projection linéaire (LLaVA), attention croisée (Flamingo) ou fusion native (Gemini). Le Visual Instruction Tuning est une stratégie efficiente pour adapter un VLM avec peu de données.

  5. Whisper (Radford et al., 2022) est un modèle encodeur-décodeur Transformer pour la reconnaissance vocale, entrainé sur 680 000 heures d’audio couvrant 96 langues. Son architecture multitâche unifie transcription, traduction et détection de langue.

  6. Les architectures unifiées (Gemini, GPT-4o) tendent vers des modèles nativement multimodaux, entrainés de bout en bout sur toutes les modalités. Les trois stratégies de fusion — précoce, tardive et croisée — offrent différents compromis entre profondeur d’intégration et coût d’entrainement.

  7. Les applications multimodales couvrent la compréhension de documents, l’analyse video, l’imagerie médicale et la génération d’images (DALL-E, Stable Diffusion, Midjourney). Les benchmarks multimodaux (VQAv2, MMMU, MathVista) montrent une progression rapide mais des défis persistants, notamment les hallucinations visuelles.