jdalfonso commited on
Commit
b4732f4
·
unverified ·
2 Parent(s): 41d9375 5447dfd

Merge pull request #3 from jdalfons/Nancy

Browse files
Files changed (1) hide show
  1. src/speech2.py +201 -0
src/speech2.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.optim as optim
5
+ import soundfile as sf
6
+ import torchaudio
7
+ import numpy as np
8
+ from datasets import Dataset
9
+ from transformers import (
10
+ Wav2Vec2Model,
11
+ Wav2Vec2Processor
12
+ )
13
+ from dotenv import load_dotenv
14
+ from sklearn.metrics import accuracy_score
15
+
16
+ # Charger .env pour Hugging Face API Key
17
+ load_dotenv()
18
+ HF_API_KEY = os.getenv("HF_API_KEY")
19
+
20
+ if not HF_API_KEY:
21
+ raise ValueError("Le token Hugging Face n'a pas été trouvé dans .env")
22
+
23
+ # Définition des labels pour la classification des émotions
24
+ LABELS = {"colere": 0, "neutre": 1, "joie": 2}
25
+ NUM_LABELS = len(LABELS)
26
+
27
+ # Charger le processeur et le modèle pour l'extraction de features
28
+ model_name = "facebook/wav2vec2-large-xlsr-53-french"
29
+ device = "cuda" if torch.cuda.is_available() else "cpu"
30
+
31
+ processor = Wav2Vec2Processor.from_pretrained(model_name)
32
+ feature_extractor = Wav2Vec2Model.from_pretrained(model_name).to(device)
33
+
34
+ # Resampleur pour convertir en 16 kHz
35
+ resampler = torchaudio.transforms.Resample(orig_freq=48_000, new_freq=16_000)
36
+
37
+ # Définition du classifieur amélioré
38
+ class EmotionClassifier(nn.Module):
39
+ def __init__(self, feature_dim, num_labels):
40
+ super(EmotionClassifier, self).__init__()
41
+ self.fc1 = nn.Linear(feature_dim, 256)
42
+ self.relu = nn.ReLU()
43
+ self.dropout = nn.Dropout(0.3)
44
+ self.fc2 = nn.Linear(256, num_labels)
45
+
46
+ def forward(self, x):
47
+ x = self.fc1(x)
48
+ x = self.relu(x)
49
+ x = self.dropout(x)
50
+ return self.fc2(x)
51
+
52
+ # Instancier le classifieur
53
+ classifier = EmotionClassifier(feature_extractor.config.hidden_size, NUM_LABELS).to(device)
54
+
55
+ # Charger les fichiers audio et leurs labels
56
+ def load_audio_data(data_dir):
57
+ data = []
58
+ for label_name, label_id in LABELS.items():
59
+ label_dir = os.path.join(data_dir, label_name)
60
+ for file in os.listdir(label_dir):
61
+ if file.endswith(".wav"):
62
+ file_path = os.path.join(label_dir, file)
63
+ data.append({"path": file_path, "label": label_id})
64
+ return Dataset.from_list(data)
65
+
66
+ # Chargement du dataset
67
+ data_dir = "./dataset"
68
+ ds = load_audio_data(data_dir)
69
+
70
+ # Charger les fichiers audio avec SoundFile et rééchantillonner à 16 kHz
71
+ def preprocess_audio(batch):
72
+ speech, sample_rate = sf.read(batch["path"], dtype="float32")
73
+
74
+ if sample_rate != 16000:
75
+ speech = torch.tensor(speech).unsqueeze(0)
76
+ speech = resampler(speech).squeeze(0).numpy()
77
+
78
+ batch["speech"] = speech.tolist() # Convertir en liste pour éviter les erreurs de PyArrow
79
+ batch["sampling_rate"] = 16000
80
+ return batch
81
+
82
+ ds = ds.map(preprocess_audio)
83
+
84
+ # Vérifier la distribution des longueurs des fichiers audio
85
+ lengths = [len(sample["speech"]) for sample in ds]
86
+ max_length = int(np.percentile(lengths, 95))
87
+
88
+ # Transformer l'audio en features utilisables par le modèle
89
+ def prepare_features(batch):
90
+ features = processor(
91
+ batch["speech"],
92
+ sampling_rate=16000,
93
+ padding=True,
94
+ truncation=True,
95
+ max_length=max_length,
96
+ return_tensors="pt"
97
+ )
98
+ batch["input_values"] = features.input_values.squeeze(0)
99
+ batch["label"] = torch.tensor(batch["label"], dtype=torch.long)
100
+ return batch
101
+
102
+ ds = ds.map(prepare_features)
103
+
104
+ # Diviser les données en train et test
105
+ ds = ds.train_test_split(test_size=0.2)
106
+ train_ds = ds["train"]
107
+ test_ds = ds["test"]
108
+
109
+ # Fonction d'évaluation sur les données de test
110
+ def evaluate(classifier, feature_extractor, test_ds):
111
+ classifier.eval()
112
+ correct = 0
113
+ total = 0
114
+
115
+ with torch.no_grad():
116
+ for batch in test_ds:
117
+ input_values = processor(
118
+ batch["speech"],
119
+ sampling_rate=16000,
120
+ return_tensors="pt",
121
+ padding=True,
122
+ truncation=True,
123
+ max_length=max_length
124
+ ).input_values.to(device)
125
+
126
+ features = feature_extractor(input_values).last_hidden_state.mean(dim=1)
127
+ logits = classifier(features)
128
+ predictions = logits.argmax(dim=-1)
129
+ labels = torch.tensor(batch["label"], dtype=torch.long, device=device)
130
+
131
+ correct += (predictions == labels).sum().item()
132
+ total += 1
133
+
134
+ return correct / total
135
+
136
+ # Fonction d'entraînement
137
+ def train_classifier(feature_extractor, classifier, train_ds, test_ds, epochs=10, batch_size=16):
138
+ optimizer = optim.Adam(classifier.parameters(), lr=1e-4)
139
+ scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.7)
140
+ loss_fn = nn.CrossEntropyLoss()
141
+
142
+ best_accuracy = 0.0 # Variable pour stocker la meilleure accuracy
143
+
144
+ for epoch in range(epochs):
145
+ classifier.train()
146
+ total_loss, correct = 0, 0
147
+ batch_count = 0
148
+
149
+ for i in range(0, len(train_ds), batch_size):
150
+ batch = train_ds[i: i + batch_size]
151
+ optimizer.zero_grad()
152
+
153
+ input_values = processor(
154
+ batch["speech"],
155
+ sampling_rate=16000,
156
+ return_tensors="pt",
157
+ padding=True,
158
+ truncation=True,
159
+ max_length=max_length
160
+ ).input_values.to(device)
161
+
162
+ with torch.no_grad():
163
+ features = feature_extractor(input_values).last_hidden_state.mean(dim=1)
164
+ features = (features - features.mean()) / features.std() # Normalisation
165
+
166
+ logits = classifier(features)
167
+ labels = torch.tensor(batch["label"], dtype=torch.long, device=device)
168
+
169
+ if labels.numel() == 0:
170
+ continue
171
+
172
+ loss = loss_fn(logits, labels)
173
+ loss.backward()
174
+ optimizer.step()
175
+
176
+ total_loss += loss.item()
177
+ correct += (logits.argmax(dim=-1) == labels).sum().item()
178
+ batch_count += 1
179
+
180
+ train_acc = correct / len(train_ds)
181
+ test_acc = evaluate(classifier, feature_extractor, test_ds)
182
+ scheduler.step()
183
+
184
+ # Sauvegarde uniquement si l'accuracy sur test est la meilleure obtenue
185
+ if test_acc > best_accuracy:
186
+ best_accuracy = test_acc
187
+ torch.save({
188
+ "classifier_state_dict": classifier.state_dict(),
189
+ "feature_extractor_state_dict": feature_extractor.state_dict(),
190
+ "processor": processor
191
+ }, "best_emotion_model.pth")
192
+ print(f"✅ Nouveau meilleur modèle sauvegardé ! Accuracy Test: {best_accuracy:.4f}")
193
+
194
+ print(f"Epoch {epoch+1}/{epochs} - Loss: {total_loss/batch_count:.4f} - Train Accuracy: {train_acc:.4f} - Test Accuracy: {test_acc:.4f}")
195
+
196
+ return classifier
197
+
198
+ # Entraînement
199
+ trained_classifier = train_classifier(feature_extractor, classifier, train_ds, test_ds, epochs=10, batch_size=16)
200
+
201
+ print("✅ Entraînement terminé, le meilleur modèle a été sauvegardé !")