File size: 15,331 Bytes
1a92342
 
 
 
eae6d52
1a92342
 
 
eae6d52
 
1a92342
 
 
 
618e7e7
1a92342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618e7e7
1a92342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49187e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
---
license: mit
language:
- fr
pipeline_tag: text-generation
---
## 💾 Documentation Technique : Xadia-charlotte (SLM)

![Charlotte](http://www.image-heberg.fr/files/17614777331148386554.jpg)

Bienvenue dans l'expérience **Xadia-charlotte**. Ce Small Language Model (SLM) est un outil d'**exploration lexicale** conçu pour les auteurs-compositeurs cherchant à briser le blocage créatif en travaillant à partir de fragments de langage non structurés.

### ⚙️ Philosophie du Modèle

**Xadia-charlotte** n'est **PAS** un générateur de texte traditionnel. Il est votre partenaire pour le **"décorticage créatif"**.

* **Fonctionnement :** Le modèle a été entraîné sur un corpus de chansons de haute qualité (créé par Clemylia), mais il est paramétré pour générer des séquences avec une **diversité lexicale maximale** et une **faible cohérence sémantique/syntaxique**.
* **Objectif :** Générer une **matière première brute** (mots, ponctuations, fragments) que l'utilisateur doit **trier, réorganiser et interpréter** pour y découvrir de nouvelles idées, métaphores ou tournures de phrases.
* **Thèmes Cibles :** Le vocabulaire généré sera majoritairement orienté vers l'**Espoir** ✨, l'**Amitié** 🤝, et l'**Écologie** 🌳.

### 🛠️ Mode d'Utilisation (Le Prompt)

Le modèle fonctionne uniquement par **complétion de séquence**. Vous devez lui fournir un début de refrain ou de couplet (le *prompt*) pour amorcer la génération.

#### 1. Formuler votre Prompt (Début de Chanson)

Le prompt idéal est un début de phrase ou une phrase complète qui **pose le contexte thématique** souhaité.

| Thème Cible | Exemple de Prompt |
| :--- | :--- |
| **Espoir** ✨ | `Malgré la nuit qui tombe, je vois encore...` |
| **Amitié** 🤝 | `Ce lien qui nous unit est comme...` |
| **Écologie** 🌳 | `Quand le vent se lève, il porte la voix...` |

> ⚠️ **Note Importante :** Ne demandez **JAMAIS** une action au modèle (`Génère une idée sur...`). Le prompt doit être une **séquence de mots** à compléter.

#### 2. Interpréter la Sortie (L'Art de l'Extraction)

La sortie de **Xadia-charlotte** sera une longue chaîne de mots, d'articles, et de ponctuations, souvent creative.

**Exemple de Sortie (après le prompt) :**
`un le , je n’a qui , est s de , rêves , qui plus , pas , , est suis ne , est je perdu , très : ne , le ne , , , la de sans frais n’a de , de . rien , y , tour est`

**Processus d'Extraction/Idéation :**

1.  **Scanner pour les Mots-Clés :** Lisez rapidement la séquence et notez les mots porteurs de sens : `rêves`, `perdu`, `frais`, `guide`, `flamme`, `trésor`.
2.  **Former des Groupes de Mots :** Assemblez les mots-clés qui résonnent ensemble, ignorant les articles ou la ponctuation parasites :
    * `rêves perdu`
    * `flamme trésor`
    * `sans frais`
3.  **Construire l'Idée :** Utilisez ces fragments pour *inspirer* de nouvelles lignes pour votre chanson.

| Fragment Généré | Interprétation (Ligne de Chanson) |
| :--- | :--- |
| `flamme trésor` | "Notre amitié est une **flamme** qui garde le **trésor** au chaud." |
| `rêves perdu guide` | "Même si je me sens **perdu**, mes **rêves** sont mon seul **guide**." |

### 🛑 Limites & Responsabilité de l'Auteur

* **Cohérence Zéro :** N'attendez **aucune** cohérence syntaxique ou sémantique. Le modèle est intentionnellement sous-optimisé pour cette tâche afin de stimuler une pensée non linéaire.
* **La Créativité est Manuelle :** L'utilisateur est le seul responsable de la **soudure, du rythme, des rimes et de la structure** de la chanson finale. Xadia-charlotte n'est qu'un **dictionnaire de suggestions par probabilité**.

exemple de code d'utilisation :

```
# ==============================================================================
#                      CHARGEMENT ET TEST DE XADIA-CHARLOTTE
# ==============================================================================

# 1. INSTALLATION DES LIBRAIRIES
# !pip install torch huggingface_hub

import torch
import torch.nn as nn
from huggingface_hub import hf_hub_download
import json
import collections
import math
import re

# --- PARAMÈTRES DE CHARGEMENT ---
# ⚠️ REMPLACEZ CETTE VALEUR PAR VOTRE ID DE MODÈLE RÉEL !
MODEL_ID = "Clemylia/Xadia-Charlotte"
TEMP_DIR = "./xadia_charlotte_download"
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'

# ==============================================================================
# 2. REDÉFINITION DES CLASSES ORIGINALES (CRUCIAL)
# ==============================================================================

# Nous devons redéfinir les classes utilisées pour l'entraînement car elles ne sont 
# pas dans la bibliothèque standard de Hugging Face.

# --- Redéfinition des Constantes Spéciales ---
UNK_TOKEN = "[UNK]"
PAD_TOKEN = "[PAD]"
SOS_TOKEN = "[SOS]"
EOS_TOKEN = "[EOS]"

# --- Redéfinition du Tokenizer Word-Level ---
class WordLevelTokenizer:
    """
    Tokenizer simple basé sur le niveau du mot, adapté pour le chargement.
    Il chargera la carte vocabulaire directement à partir du fichier vocab.json.
    """
    def __init__(self, vocab_map):
        # vocab_map est le dictionnaire {mot: id} chargé depuis HF
        self.word_to_id = vocab_map
        self.id_to_word = [None] * len(vocab_map)
        for word, id_ in vocab_map.items():
            self.id_to_word[id_] = word
            
        # Récupération des IDs spéciaux
        self._pad_token_id = self.word_to_id.get(PAD_TOKEN)
        self._sos_token_id = self.word_to_id.get(SOS_TOKEN)
        self._eos_token_id = self.word_to_id.get(EOS_TOKEN)
        self._unk_token_id = self.word_to_id.get(UNK_TOKEN)

    def encode(self, text):
        """Convertit une phrase en liste d'IDs (utilise la même logique de nettoyage)."""
        text = text.lower()
        # Logique de gestion de l'apostrophe (doit correspondre à l'entraînement !)
        text = re.sub(r"([cjlmnst])'", r'\1 ', text)
        for punc in '.,!?:;()"-':
            text = text.replace(punc, f' {punc} ')
            
        words = text.split()
        
        return [self.word_to_id.get(word, self._unk_token_id) for word in words]

    def decode(self, token_ids):
        """Convertit une liste d'IDs en phrase."""
        return " ".join([self.id_to_word[id_.item()] if isinstance(id_, torch.Tensor) else self.id_to_word[id_] 
                         for id_ in token_ids if id_ < len(self.id_to_word)])

    @property
    def vocab_size_final(self): return len(self.word_to_id)
    @property
    def pad_token_id(self): return self._pad_token_id
    @property
    def sos_token_id(self): return self._sos_token_id
    @property
    def eos_token_id(self): return self._eos_token_id


# --- Redéfinition des Blocs d'Architecture (CausalSelfAttention, XadiaBlock) ---
# (Ces classes sont complexes et n'ont pas besoin d'être modifiées si elles sont 
# identiques à celles de l'entraînement.)

class CausalSelfAttention(nn.Module):
    def __init__(self, d_model, num_heads, dropout):
        super().__init__()
        self.d_model = d_model
        self.num_heads = num_heads
        self.head_dim = d_model // num_heads

        self.q_proj = nn.Linear(d_model, d_model)
        self.k_proj = nn.Linear(d_model, d_model)
        self.v_proj = nn.Linear(d_model, d_model)
        self.out_proj = nn.Linear(d_model, d_model)
        
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        B, T, C = x.size()
        q = self.q_proj(x).view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
        k = self.k_proj(x).view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
        v = self.v_proj(x).view(B, T, self.num_heads, self.head_dim).transpose(1, 2)

        attn_scores = (q @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)

        causal_mask = torch.triu(torch.ones(T, T), diagonal=1).bool().to(x.device)
        attn_scores = attn_scores.masked_fill(causal_mask, float('-inf'))

        attn_weights = nn.functional.softmax(attn_scores, dim=-1)
        attn_weights = self.dropout(attn_weights)
        
        output = attn_weights @ v
        
        output = output.transpose(1, 2).contiguous().view(B, T, C)
        output = self.out_proj(output)
        
        return output

class XadiaBlock(nn.Module):
    def __init__(self, d_model, num_heads, ffn_factor, dropout):
        super().__init__()
        self.norm1 = nn.LayerNorm(d_model)
        self.attn = CausalSelfAttention(d_model, num_heads, dropout)
        self.norm2 = nn.LayerNorm(d_model)
        
        self.ffn = nn.Sequential(
            nn.Linear(d_model, d_model * ffn_factor),
            nn.GELU(),
            nn.Linear(d_model * ffn_factor, d_model),
            nn.Dropout(dropout)
        )
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        x = x + self.dropout(self.attn(self.norm1(x)))
        x = x + self.dropout(self.ffn(self.norm2(x)))
        return x

# --- Redéfinition de la Classe Modèle Principale ---
class XadiaCharlotteSLM(nn.Module):
    def __init__(self, vocab_size, d_model, num_layers, num_heads, ffn_factor, max_seq_length, dropout):
        super().__init__()
        self.max_seq_length = max_seq_length

        self.word_embedding = nn.Embedding(vocab_size, d_model)
        self.position_embedding = nn.Embedding(max_seq_length, d_model)
        self.dropout = nn.Dropout(dropout)
        self.transformer_blocks = nn.ModuleList([
            XadiaBlock(d_model, num_heads, ffn_factor, dropout) 
            for _ in range(num_layers)
        ])
        self.norm_final = nn.LayerNorm(d_model)
        self.lm_head = nn.Linear(d_model, vocab_size, bias=False)
        
        # Pas besoin d'init weights ici, car nous chargeons des poids entraînés

    def forward(self, input_ids):
        B, T = input_ids.size()
        
        token_emb = self.word_embedding(input_ids)
        position_ids = torch.arange(0, T, dtype=torch.long, device=input_ids.device)
        position_emb = self.position_embedding(position_ids)
        
        x = self.dropout(token_emb + position_emb)

        for block in self.transformer_blocks:
            x = block(x)

        x = self.norm_final(x)
        logits = self.lm_head(x)

        return logits


# ==============================================================================
# 3. CHARGEMENT DU MODÈLE DEPUIS HUGGING FACE
# ==============================================================================

def load_xadia_charlotte(model_id, device):
    """
    Télécharge les fichiers de config, vocabulaire et poids, et instancie le modèle.
    """
    print(f"--- ⬇️ TÉLÉCHARGEMENT DE {model_id} ---")
    
    # Téléchargement des fichiers essentiels
    try:
        # Configuration
        config_path = hf_hub_download(repo_id=model_id, filename="config.json", local_dir=TEMP_DIR)
        with open(config_path, 'r') as f:
            config = json.load(f)
        print(f"Configuration chargée: {config['vocab_size']} mots, {config['num_layers']} couches.")

        # Vocabulaire
        vocab_path = hf_hub_download(repo_id=model_id, filename="vocab.json", local_dir=TEMP_DIR)
        with open(vocab_path, 'r', encoding='utf-8') as f:
            vocab_map = json.load(f)
        
        # Poids
        weights_path = hf_hub_download(repo_id=model_id, filename="pytorch_model.bin", local_dir=TEMP_DIR)
    
    except Exception as e:
        print(f"ERREUR lors du téléchargement. Assurez-vous que l'ID de modèle est correct et que les fichiers sont présents. Détail: {e}")
        return None, None

    # 1. Initialisation du Tokenizer
    tokenizer = WordLevelTokenizer(vocab_map)
    
    # 2. Initialisation du Modèle
    model = XadiaCharlotteSLM(
        vocab_size=config['vocab_size'],
        d_model=config['d_model'],
        num_layers=config['num_layers'],
        num_heads=config['num_heads'],
        ffn_factor=config['ffn_factor'],
        max_seq_length=config['max_seq_length'],
        dropout=config['dropout']
    ).to(device)

    # 3. Chargement des poids
    model.load_state_dict(torch.load(weights_path, map_location=device))
    model.eval() # Mode évaluation pour l'inférence
    print("Modèle Xadia-Charlotte chargé et prêt.")
    
    return model, tokenizer

# ==============================================================================
# 4. FONCTION DE GÉNÉRATION (TEST)
# ==============================================================================

def generate_text(model, tokenizer, prompt, max_new_tokens=100, temperature=0.9):
    """
    Génère du texte conditionné par un prompt (refrain).
    """
    # 1. Préparation du prompt
    prompt_ids = tokenizer.encode(prompt)
    if not prompt_ids:
        print("Erreur: Prompt vide ou ne contient aucun mot reconnu.")
        return
        
    input_ids = torch.tensor([prompt_ids], dtype=torch.long).to(model.lm_head.weight.device)
    
    print(f"\n--- 🎶 TEST DE GÉNÉRATION (Temp={temperature:.2f}) ---")
    print(f"Prompt (Refrain): {prompt}")
    print("-" * 50)
    
    generated_tokens = prompt_ids
    
    with torch.no_grad():
        for _ in range(max_new_tokens):
            # Limiter l'input au contexte max
            input_context = input_ids[:, -model.max_seq_length:] 
            
            # Forward Pass : obtenir les logits
            logits = model(input_context)
            
            # Seul le dernier token prédit nous intéresse
            last_token_logits = logits[0, -1, :] / temperature
            
            # Échantillonnage (Sampling)
            probs = torch.nn.functional.softmax(last_token_logits, dim=-1)
            next_token_id = torch.multinomial(probs, num_samples=1)
            
            # Arrêt si EOS est généré
            if next_token_id.item() == tokenizer.eos_token_id:
                break
            
            # Mise à jour de la séquence
            input_ids = torch.cat([input_ids, next_token_id.unsqueeze(0)], dim=-1)
            generated_tokens.append(next_token_id.item())

    # Décodage
    prompt_len = len(tokenizer.encode(prompt))
    generated_couplet = tokenizer.decode(generated_tokens[prompt_len:])
    
    print(f"Résultat Généré (Couplet): {generated_couplet}")
    print("-" * 50)


# ==============================================================================
# 5. EXÉCUTION
# ==============================================================================

if __name__ == '__main__':
    
    # ⚠️ IMPORTANT : REMPLACER PAR VOTRE ID DE MODÈLE PUBLIÉ
    # Exemple : "Clemylia/Xadia-Charlotte"
    YOUR_MODEL_ID = "Clemylia/Xadia-Charlotte"
    
    # 1. Chargement du Modèle et du Tokenizer
    model, tokenizer = load_xadia_charlotte(YOUR_MODEL_ID, DEVICE)

    if model and tokenizer:
        # 2. Test du Modèle
        prompt_refrain = "quand la lumière s'éteint, mon cœur s'allume pour toi"
        
        # Test 1: Créativité (Température élevée)
        generate_text(model, tokenizer, prompt_refrain, max_new_tokens=150, temperature=0.9) 
        
        # Test 2: Cohérence (Température faible)
        generate_text(model, tokenizer, prompt_refrain, max_new_tokens=150, temperature=0.5)
```