Comment j'ai formé un codeur croisé Stance-Aware qui classe les titres d'actualité indonésiens par rapport aux allégations — en commençant par un Colab TPU gratuit et en passant à Cloud TPU v5p avec un seul@kinétique.run() décorateur

Introduction

La désinformation est l’un des problèmes déterminants de l’ère des médias sociaux, et l'Indonésie a été particulièrement durement touchée. Canular (le raccourci indonésien pour les fausses nouvelles) se propager à travers les groupes WhatsApp et les fils de discussion Twitter plus rapidement que n'importe quel vérificateur de faits ne peut suivre. La plupart des recherches publiées sur la détection automatisée des fausses nouvelles se concentrent sur les données en anglais, ce qui laisse les praticiens travaillant avec Bahasa Indonesia dans une situation frustrante: les techniques existent, mais les outils et les modèles pré-entraînés sont rares.

Cet article explique la construction d'un véritable, fonctionnement détecteur de canular multimodal pour les nouvelles indonésiennes à partir de zéro. Le modèle prend deux entrées — a réclamer (l'affirmation originale, souvent sur les réseaux sociaux) et un titre (un titre d'article de presse qui mentionne le même sujet) — et prédit si l'article prend en charge la réclamation (pour), réfute il (contre), ou simplement observe de manière neutre (observer).

L'architecture est un Encodeur croisé sensible à la position: un encodeur de style BiLSTM pour chaque entrée, auto-attention multi-tête, et une couche d'attention croisée qui permet à la revendication et au titre de se lire littéralement avant la classification. Construit de bout en bout avec JAX et Lin, formé sur TPU.

L'histoire du déploiement comporte deux moitiés:

  1. Colab TPU gratuit pour le prototypage — Google donne à chaque utilisateur de Colab un accès gratuit à un TPU v5e-1, ce qui est suffisant pour entraîner ce modèle de bout en bout en moins d'une heure sans frais.
  2. Cloud TPU v5p via Keras Kinetic pour une formation sérieuse — lorsque vous dépassez les limites d'exécution de Colab, Cinétique dur vous permet d'envoyer la même fonction de formation à un pod Cloud TPU avec un seul décorateur Python. Pas de Docker, pas de YAML Kubernetes, pas de SSH.

À la fin, tu auras:

  • Un tokeniseur indonésien réutilisable et un chargeur d'ensembles de données
  • Un encodeur Transformer à 4 couches avec une attention croisée sensible à la position, écrit en lin
  • Une boucle de formation compilée JIT avec optax et orbaxcheckpointing
  • Un prédict.py fonctionnel qui exécute de nouvelles paires revendication-titre via le modèle formé
  • Exactement le même code, déployé sur Cloud TPU via@kinétique.run()

Allons-y.

Pourquoi ce problème est difficile (et intéressant)

Des détecteurs naïfs de fausses nouvelles examinent un morceau de texte et tentent de le classer comme « vrai » ou « faux ». C’est à la fois techniquement faible et éthiquement inconfortable — un seul texte véhicule rarement suffisamment de signal, et le cadrage « vrai/faux » suppose que le modèle a accès à une vérité terrain qu'il ne peut pas avoir.

Le détection de position le cadrage est beaucoup plus honnête. Étant donné une réclamation et un article de presse connexe, le modèle ne décide pas si la revendication est vrai; il décide si cet article particulier prend en charge, réfute, ou observe simplement la revendication. C’est une question à laquelle un modèle peut réellement répondre, et c'est exactement l'entrée dont un vérificateur de faits en aval a besoin pour passer un dernier appel.

Mathématiquement, la tâche est une classification à 3 niveaux surinteraction de deux morceaux de texte. Ce mot — interaction — est ce qui rend l’architecture intéressante. Vous ne pouvez pas simplement encoder chaque côté indépendamment et concaténer. Vous avez besoin d'un calque qui permette à la revendication de s'occuper du titre et vice versa, afin que le modèle puisse capter des indices subtils comme la négation (« Le gouvernement nie… »), couverture ("allégué…"), ou encadrement ("selon les critiques...").

Pourquoi JAX, Lin, Cinétique dur, et TPU?

  • JAX me donne du code de style NumPy avec différenciation automatique, Compilation JIT via XLA, et accélération transparente sur CPU/GPU/TPU.
  • Lin se trouve au-dessus de JAX et me permet d'écrire des réseaux de neurones en tant que classes nn.Module. Le modèle est dense en couches d'attention, et Flax garde la gestion des paramètres propre.
  • Optax pour l'optimisation (AdamW avec échauffement linéaire + désintégration du cosinus) et Orbax pour les points de contrôle  — les deux font partie de l'écosystème JAX et sont compatibles JIT.
  • Cinétique dur est la colle de déploiement. Un décorateur transforme une fonction Python locale en une tâche TPU distante, avec mise en cache des conteneurs, diffusion en continu des journaux, et provisionnement automatique de GKE.
  • TPU parce que la charge de travail est dominée par les matmuls d'attention — exactement pour quoi les réseaux systoliques TPU sont conçus. Gratuit sur Colab (v5e-1), et Cloud TPU v5p lorsque vous avez besoin d'évoluer.

TPU vs GPU pour cette charge de travail

Un Transformer multimodal avec attention croisée est l'une des charges de travail TPU les plus propres que vous puissiez écrire. Voici pourquoi, et où les GPU tiennent toujours bon.

Conception du matériel

  • GPU (Nvidia A100/H100): processeur parallèle à usage général, des milliers de cœurs CUDA, idéal pour le calcul parallèle arbitraire.
  • TPU (v5e ou v5p): accélérateur spécifique à un domaine construit autour d'un large réseau systolique (MXU) optimisé pour les multiplications matricielles denses.

Qu'est-ce qui domine le calcul dans ce modèle

  • Auto-attention multi-têtes: softmax(QKᵀ / √d) V — trois gros matmuls par tête et par couche.
  • Attention croisée entre la revendication et le titre: même forme, juste avec différentes entrées alimentant Q vs K/V.
  • Blocs de rétroaction: deux couches denses avec GELU entre elles.

Ce sont tous des matmuls denses aux formes prévisibles. Le réseau systolique TPU est spécialement conçu pour comprendre exactement ce modèle aux pics de FLOP.. Le compilateur XLA fusionne l'intégralité du train_step en quelques noyaux, et après la première compilation, chaque étape s'exécute à plein débit.

Là où les GPU gagnent toujours en matière de détection de position / PNL

  • Vous effectuez un décodage au niveau du jeton avec un cache KV et des longueurs de génération irrégulières (nous ne sommes pas — nous faisons de la classification).
  • Vous avez besoin d'un modèle de transformateurs HuggingFace qui n'est disponible qu'en tant que point de contrôle PyTorch (nous nous entraînons à partir de zéro, donc cela ne s'applique pas).
  • Vous souhaitez effectuer une itération dans un notebook avec un flux de contrôle Python constant qui ne fonctionne pas proprement en JIT (Colab vous offre à tous les deux un TPU etun cahier, donc tu n'as pas à choisir).

Où les TPU gagnent pour la détection de position / PNL

  • Séquences de longueur fixe (nous nous dirigeons vers 64 jetons) → formes prévisibles → superbe compilation XLA.
  • L'ensemble des JIT train_step dans un seul graphe d'exécution fusionné.
  • pmap / shard_map fait de la formation multi-puces une solution unique si vous souhaitez évoluer.
  • Gratuit sur Colab, et Cloud TPU v5e coûte environ 0,40 $/heure de puce sur Spot.

Règle générale

  • Prototypage rapide dans un notebook avec des données Bahasa Indonesia → Colab TPU gratuit. (Cet article.)
  • R itératif&D en utilisant les points de contrôle HuggingFace PyTorch → GPU.
  • Formation en production avec batchable, Charges de travail natives JAX → Cloud TPU via Kinetic.
  • Vous devez peaufiner un LLM indonésien 7B+ → c'est un autre article (et une catégorie différente — vLLM ou Tunix).

Maintenant, construisons-le.

Architecture du projet

Le pipeline est simple:

datasetika.csv (Réclamer, Titre, Position)


┌──────────────────┐
│ IndonesianTokenizer │ espace + ponctuation, vocabulaire du corpus
└──────────────────┘


┌──────────────────┐
│ FakeNewsDataset │ Répartition stratifiée train/val/test, Tableaux prêts pour JAX
└──────────────────┘


┌─────────────────── ───────────────────┐
│ Détecteur de fausses nouvelles (Lin nn.Module) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Jeton+Pos │ │ Jeton+Pos │ │
│ │ Intégration │ │ Intégration │ │
│ │ (Réclamer) │ │ (Titre) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Transformateur │ │ Transformateur │ │
│ │ × N couches │ │ × N couches │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Encodeur croisé de position│ │
│ │ (attention croisée + │ │
│ │ différence & produit) │ │
│ └──────────┬──────────┘ │
│ ▼ │
│ Dense → Softmax (3 cours) │
└─────────────────── ───────────────────┘


pour / contre / observer

Le tout se trouve dans un train_step compilé par jax.jit. Passons maintenant en revue chaque pièce.

Étape 1 — Configuration du matériel

Il y a deux chemins. Choisissez celui qui correspond à votre étape du projet.

Chemin A: Colab TPU gratuit (recommandé pour la première utilisation)

  1. Ouvrir colab.research.google.com et créez un nouveau bloc-notes.
  2. Cliquez Runtime → Modifier le type d'exécution.
  3. Sous Accélérateur matériel, sélectionner v5e-1TPU.
  4. Cliquez Sauvegarder.
  5. Vérifier dans une cellule:
importer jax
imprimer(jax.devices())
# Attendu: [TpuDevice(identifiant=0, ...)]

C'est ça. Vous disposez désormais d'une puce TPU v5e gratuite pour la durée de votre session Colab.

Chemin B: Cloud TPU via Keras Kinetic (quand tu es devenu trop grand pour Colab)

Colab est fantastique pour le prototypage mais a des limites d'exécution et est confronté à des contraintes de charge.. Lorsque vous êtes prêt à exécuter des tâches de formation de plusieurs heures, passer à un vrai Cloud TPU. Le chemin traditionnel consiste à provisionner une VM TPU, SSH dans, installer des dépendances, et télécharger des scripts — Kinetic ignore tout cela.

Sur votre ordinateur portable local:

pip installer keras-cinétique
Connexion par défaut de l'application d'authentification gcloud
Projet d'ensemble de configuration gcloud YOUR_PROJECT_ID
cinétique up --accélérateur v5p-8 --oui

La dernière commande provisionne un cluster GKE Autopilot avec un pool de nœuds TPU v5p-8.. Prend quelques minutes la première fois, après quoi vous ne touchez plus à l’infrastructure jusqu’au démontage.

Je vais montrer le vrai @kinétique.run() déploiement à l'étape 6. Pour l'instant, construisons le modèle.

Étape 2  —  Tokenizer et ensemble de données indonésiens

Le Bahasa Indonesia est morphologiquement moins complexe que, dire, Turc ou finnois, donc un espace + le tokenizer de ponctuation avec un vocabulaire appris fonctionne étonnamment bien comme base de référence. (Pour la production, échanger dans IndoBERT — Je vais montrer comment à la fin de cette section.)

Le tokenizer réserve quatre jetons spéciaux, construit un vocabulaire classé par fréquence à partir du corpus de formation, et émet(token_ids, attention_masque) paires à une longueur fixe. Trucs standards, mais avec une subtilité: nous tokenisons les deux la réclamation et Judul (titre) colonnes dans un commun vocabulaire afin que la couche d'intégration puisse détecter les corrélations d'entrée croisées.

"""
Prétraitement des données pour la détection des fausses nouvelles indonésiennes
Réclamation tokenisée + Titre de la colonne, encode les étiquettes de position.
Compatible avec le pipeline de formation JAX/Flax.
"""
importer re
importer numpy en tant que np
importer des pandas en tant que PD
à partir du compteur d'importation de collections
en tapant la liste d'importation, Tuple, Dicté
depuis sklearn.model_selection importer train_test_split
ÉTIQUETTE_MAP = {"for": 0, "against": 1, "observing": 2}
ID_TO_LABEL = {v: k pour k, v dans LABEL_MAP.items()}
classe IndonesianTokenizer:
"""
Espaces légers + tokeniseur de ponctuation pour le texte indonésien.
    Pour la production, échanger avec:
à partir des transformateurs importer AutoTokenizer
tok = AutoTokenizer.from_pretrained("indobenchmark/indobert-base-p1")
"""
SPECIAL_TOKENS = {"<TAMPON>": 0, "<UNK>": 1, "<CLS>": 2, "<SEP>": 3}
    par défaut __init__(soi, taille_vocabule: entier = 30_000, min_freq: entier = 2):
self.vocab_size = vocabulaire_size
self.min_freq = min_freq
soi.word2id: Dicté[str, int] = dicter(soi.SPECIAL_TOKENS)
soi.id2word: Dicté[int, str] = {v: k pour k, v dans self.word2id.items()}
    @méthodestatique
par défaut _clean(texte: str) -> str:
texte = texte.inférieur()
texte = re.sub(r"<[^>]+>", " ", texte) # supprimer HTML
texte = re.sub(r"[^\w\s]", " ", texte, flags=re.UNICODE) # garder l'alphanum
texte = re.sub(r"\s+", " ", texte).bande()
renvoyer le texte
    @méthodestatique
par déf tokeniser(texte: str) -> Liste[str]:
retourner IndonesianTokenizer._clean(texte).diviser()
 def build_vocab(soi, textes: Liste[str]) -> Aucun:
comptoir: Compteur = Compteur()
pour t dans les textes:
compteur.mise à jour(self.tokenize