
Utilisation d’un LLM local comme classificateur Zero-Shot
que « Les groupes sont remarquablement intelligents et sont souvent plus intelligents que les personnes les plus intelligentes qui les composent. » Il écrivait sur la prise de décisionmais le même principe s’applique à la classification : demandez à suffisamment de personnes de décrire le même phénomène et une taxonomie commence à émerger, même si personne ne la formule de la même manière. Le défi consiste à extraire ce signal du bruit.
J’avais plusieurs milliers de lignes de données en texte libre et je devais faire exactement cela. Chaque ligne était une courte annotation en langage naturel expliquant pourquoi une recherche de sécurité automatisée n’était pas pertinentequelles fonctions utiliser pour un correctif ou quelles pratiques de codage suivre. Une personne a écrit « Il s’agit d’un code de test, non déployé nulle part. » Un autre a écrit « environnement de non-production, qui peut être ignoré en toute sécurité. » Un troisième a écrit « ne s’exécute que dans le pipeline CI/CD pendant les tests d’intégration. » Tous les trois signifiaient la même chose, mais aucun d’entre eux ne partageait plus d’un mot ou deux.
La taxonomie était là. J’avais juste besoin du bon outil pour l’extraire. Le clustering traditionnel et la correspondance de mots clés ne pouvaient pas gérer la variation de paraphrase, j’ai donc essayé quelque chose dont je n’avais pas beaucoup parlé : utiliser un LLM hébergé localement comme classificateur zéro tir. Cet article de blog explore ses performances, son fonctionnement et quelques conseils pour utiliser et déployer ces systèmes vous-même.
Pourquoi le clustering traditionnel a du mal avec le texte libre court
Le clustering non supervisé standard fonctionne en trouvant une proximité mathématique dans certains espaces de fonctionnalités. Pour les documents longs, cela convient généralement. Il existe suffisamment de signaux dans les fréquences de mots ou dans les vecteurs intégrés pour former des groupes cohérents. Mais un texte court et sémantiquement dense brise ces hypothèses de plusieurs manières spécifiques.
L’intégration de la similarité amalgame différentes significations. « Cette clé n’est utilisée qu’en développement » et « Cette clé API est codée en dur pour plus de commodité » produisent des intégrations similaires parce que le vocabulaire se chevauche. Mais l’un concerne un environnement de non-production et l’autre concerne un compromis intentionnel en matière de sécurité. K-moyennes ou DBSCAN je ne peux pas les distinguer car les vecteurs sont trop proches.
Les modèles de sujets font apparaître des mots, pas des concepts. Allocation latente de Dirichlet (LDA) et ses variantes trouvent des modèles de cooccurrence de mots. Lorsque votre corpus est constitué d’annotations d’une phrase, le signal de cooccurrence de mots est trop clairsemé pour former des sujets significatifs. Vous obtenez des clusters définis par «test » ou « code » ou « sécurité» plutôt que des thèmes cohérents.
La correspondance des expressions régulières et des mots clés ne peut pas gérer les variations de paraphrase. Vous pourriez écrire des règles pour attraper « code d’essai » et « hors-production« , mais tu manquerais »utilisé uniquement pendant CI » « jamais déployé » « luminaire réservé au développement», et des dizaines d’autres formulations qui expriment toutes la même idée sous-jacente.
Le fil conducteur : ces méthodes opèrent sur des caractéristiques de surface (jetons, vecteurs, motifs) plutôt que sur une signification sémantique. Pour les tâches de classification où le sens compte plus que le vocabulaire, vous avez besoin de quelque chose qui comprend le langage.
Les LLM comme classificateurs zéro tir
L’idée clé est simple : au lieu de demander à un algorithme de découvrir des clusters, définissez vos catégories de candidats en fonction de vos connaissances du domaine et demandez à un modèle de langage de classer chaque entrée.
Cela fonctionne parce que les LLM traitent la signification sémantique, pas seulement les modèles de jetons. « Cette clé n’est utilisée qu’en développement » et « Environnement de non-production, à ignorer en toute sécurité » ne contiennent presque aucun mot qui se chevauche, mais un modèle de langage comprend qu’ils expriment la même idée. Ce n’est pas seulement une intuition. Chae et Davidson (2025) a comparé 10 modèles sur des régimes d’entraînement à tir zéro, à quelques tirs et affinés et a constaté que les grands LLM en mode zéro tir fonctionnaient de manière compétitive avec le BERT affiné sur les tâches de détection de position. Wang et coll. (2023) ont découvert que les LLM surpassaient les méthodes de classification de pointe sur trois des quatre ensembles de données de référence en utilisant uniquement l’invite de tir zéro, aucune donnée de formation étiquetée n’est requise.
La configuration comporte trois composants :
- Catégories de candidats. Une liste de catégories mutuellement exclusives définies à partir des connaissances du domaine. Dans mon cas, j’ai commencé avec environ 10 thèmes attendus (code de test, validation des entrées, protections du framework, environnements de non-production, etc.) et j’ai étendu à 20 candidats après avoir examiné un échantillon.
- Une invite de classification. Structuré pour renvoyer une étiquette de catégorie et une brève raison. Basse température (0,1) pour la cohérence. Sortie maximale courte (100 jetons) puisque nous n’avons besoin que d’une étiquette, pas d’un essai.
- Un LLM local. j’ai utilisé Ollama pour exécuter des modèles localement. Aucun coût d’API, aucune donnée ne quitte ma machine et suffisamment rapide pour des milliers de classifications.
Voici le cœur de l’invite de classification :
CLASSIFICATION_PROMPT = """
Classify this text into one of these themes:
{themes}
Text:
"{content}"
Respond with ONLY the theme number and name, and a brief reason.
Format: THEME_NUMBER. THEME_NAME | Reason
Classification:
"""
Et l’Ollama appelle :
response = ollama.generate(
model="gemma2",
prompt=prompt,
options={
"temperature": 0.1, # Low temp for consistent classification
"num_predict": 100, # Short response, we just need a label
}
)
Deux choses à noter. Tout d’abord, le réglage de la température est important. À 0,7 ou plus, la même entrée peut produire différentes classifications d’une exécution à l’autre. À 0,1, le modèle est presque déterministe, ce qui facilite la classification. Deuxièmement, limiter num_predict empêche le modèle de générer des explications dont vous n’avez pas besoin, ce qui accélère considérablement le débit.
Construire le pipeline
Le pipeline complet comporte trois étapes : prétraiter, classer, analyser.
Prétraitement supprime le contenu qui ajoute des jetons sans ajouter de signal de classification. URL, expressions passe-partout («Pour plus d’informations, voir…« ), et les artefacts de formatage sont tous supprimés. Les termes courants sont normalisés (« faux positif » devient « FP » « production » devient « prod » pour réduire la variation des jetons. La déduplication par hachage de contenu supprime les répétitions exactes. Cette étape a réduit mon budget de jetons d’environ 30% et a rendu la classification plus cohérente.
Classification exécute chaque entrée à travers le LLM avec les catégories de candidats. Pour environ 7 000 entrées, cela a pris environ 45 minutes sur un MacBook Pro utilisant Gemme 2 (Paramètres 9B). j’ai aussi testé Lama 3.2 (3B), qui était plus rapide mais légèrement moins précis sur les cas extrêmes où deux catégories étaient proches. Gemma 2 a traité les entrées ambiguës avec un jugement nettement meilleur.
Un problème pratique : les longs tirages peuvent échouer en cours de route. Le pipeline enregistre les points de contrôle toutes les 100 classifications, afin que vous puissiez reprendre là où vous vous étiez arrêté.
Analyse regroupe les résultats et génère un tableau de distribution. Voici à quoi ressemblait le résultat :

Le graphique raconte une histoire claire. Plus d’un quart de toutes les entrées décrivaient du code qui s’exécute uniquement dans des environnements hors production. 21,9 % ont décrit des cas dans lesquels un cadre de sécurité gère déjà le risque. Ces deux catégories représentent à elles seules la moitié de l’ensemble de données, ce qui constitue un type d’informations difficile à extraire d’un texte non structuré autrement.
Quand cette approche n’est pas la bonne solution
Cette technique fonctionne mieux dans un créneau spécifique : ensembles de données à échelle moyenne (des centaines à des dizaines de milliers d’entrées), des textes sémantiquement complexes et des situations dans lesquelles vous disposez de suffisamment de connaissances du domaine pour définir des catégories de candidats mais pas de données de formation étiquetées.
C’est pas le bon outil quand :
- vos catégories sont définies par mots-clés (utilisez simplement regex),
- lorsque vous avez étiqueté les données d’entraînement (former un classificateur supervisé ; ce sera plus rapide et moins cher),
- lorsque vous avez besoin d’une latence inférieure à la seconde à grande échelle (utilisez des intégrations et une recherche du voisin le plus proche),
- ou quand vous ne savez vraiment pas quelles catégories existent. Dans ce cas, exécutez d’abord une modélisation thématique exploratoire pour développer l’intuition, puis passez à la classification LLM une fois que vous pouvez définir des catégories.
L’autre contrainte est le débit. Même sur une machine rapide, classer une entrée par fraction de seconde signifie que 7 000 entrées prennent près d’une heure. Pour les ensembles de données supérieurs à 100 000 entrées, vous aurez besoin d’un modèle hébergé par une API ou d’une stratégie de traitement par lots.
Autres applications à essayer
Le pipeline se généralise à tout problème pour lequel vous avez du texte non structuré et avez besoin de catégories structurées.
Commentaires des clients. Les réponses NPS, les tickets d’assistance et les questions ouvertes d’enquête souffrent tous du même problème : une formulation variée pour un ensemble fini de thèmes sous-jacents. « Votre application plante à chaque fois que j’ouvre les paramètres» et « Sla page des paramètres est cassée sur iOS » sont la même catégorie, mais la correspondance des mots clés ne le détectera pas.
Tri des rapports de bugs. Les descriptions de bogues en texte libre peuvent être automatiquement classées par composant, cause première ou gravité. Ceci est particulièrement utile lorsque la personne qui signale le bug ne sait pas quel composant est responsable.
Classification des intentions de code. C’est celui que je n’ai pas encore essayé mais que je trouve convaincant : classer les extraits de code, Règles Semgrepou des règles de configuration par finalité (authentification, accès aux données, gestion des erreurs, journalisation). La même technique s’applique. Définissez les catégories, rédigez une invite de classification, exécutez le corpus via un modèle local.
Commencer
Le pipeline est simple : définissez vos catégories, rédigez une invite de classification et exécutez vos données via un modèle local.
Le plus difficile n’est pas le code. Il s’agit de définir des catégories mutuellement exclusives et collectivement exhaustives. Mon conseil : commencez avec un échantillon de 100 candidatures, classez-les manuellement, notez les catégories que vous continuez à rechercher et utilisez-les comme liste de candidats. Ensuite, laissez le LLM mettre à l’échelle le modèle.
J’ai utilisé cette technique dans le cadre d’un analyse plus approfondie de la manière dont les équipes de sécurité corrigent les vulnérabilités. Les résultats de la classification ont permis de déterminer quels types de contextes de sécurité sont les plus courants dans les organisations, et le graphique ci-dessus est l’un des résultats de ce travail. Si l’aspect sécurité vous intéresse, le rapport complet est disponible sur ce lien.



