jesse-tong commited on
Commit
cb428cb
·
1 Parent(s): 6372b0e

Change loss function for multi-category because CrossEntropyLoss only accept outputs of (batch, x) and labels with shape of (batch)

Browse files
Files changed (2) hide show
  1. knowledge_distillation.py +34 -2
  2. trainer.py +119 -91
knowledge_distillation.py CHANGED
@@ -91,7 +91,23 @@ class DistillationTrainer:
91
  distill_loss = F.kl_div(soft_prob, soft_targets, reduction='batchmean') * (temperature ** 2)
92
 
93
  # Standard cross entropy with hard targets
94
- ce_loss = self.ce_loss(student_logits, labels)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  # Weighted combination of the two losses
97
  loss = alpha * distill_loss + (1 - alpha) * ce_loss
@@ -228,7 +244,23 @@ class DistillationTrainer:
228
  )
229
 
230
  # Calculate regular CE loss (no distillation during evaluation)
231
- loss = self.ce_loss(student_logits, labels)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  eval_loss += loss.item()
233
 
234
  # Get predictions
 
91
  distill_loss = F.kl_div(soft_prob, soft_targets, reduction='batchmean') * (temperature ** 2)
92
 
93
  # Standard cross entropy with hard targets
94
+ if self.num_categories > 1:
95
+ total_loss = 0
96
+ for i in range(self.num_categories):
97
+ start_idx = i * self.num_classes
98
+ end_idx = (i + 1) * self.num_classes
99
+ category_outputs = student_logits[:, start_idx:end_idx] # Shape (batch, num_classes)
100
+ category_labels = labels[:, i] # Shape (batch)
101
+
102
+ # Ensure category_labels are in [0, self.num_classes - 1]
103
+ if category_labels.max() >= self.num_classes or category_labels.min() < 0:
104
+ print(f"ERROR: Category {i} labels out of range [0, {self.num_classes - 1}]: min={category_labels.min()}, max={category_labels.max()}")
105
+
106
+ total_loss += self.criterion(category_outputs, category_labels)
107
+
108
+ ce_loss = total_loss / self.num_categories # Average loss
109
+ else:
110
+ ce_loss = self.ce_loss(student_logits, labels)
111
 
112
  # Weighted combination of the two losses
113
  loss = alpha * distill_loss + (1 - alpha) * ce_loss
 
244
  )
245
 
246
  # Calculate regular CE loss (no distillation during evaluation)
247
+ if self.num_categories > 1:
248
+ total_loss = 0
249
+ for i in range(self.num_categories):
250
+ start_idx = i * self.num_classes
251
+ end_idx = (i + 1) * self.num_classes
252
+ category_outputs = student_logits[:, start_idx:end_idx] # Shape (batch, num_classes)
253
+ category_labels = labels[:, i] # Shape (batch)
254
+
255
+ # Ensure category_labels are in [0, self.num_classes - 1]
256
+ if category_labels.max() >= self.num_classes or category_labels.min() < 0:
257
+ print(f"ERROR: Category {i} labels out of range [0, {self.num_classes - 1}]: min={category_labels.min()}, max={category_labels.max()}")
258
+
259
+ total_loss += self.criterion(category_outputs, category_labels)
260
+
261
+ loss = total_loss / self.num_categories # Average loss
262
+ else:
263
+ loss = self.ce_loss(student_logits, labels)
264
  eval_loss += loss.item()
265
 
266
  # Get predictions
trainer.py CHANGED
@@ -79,106 +79,117 @@ class Trainer:
79
  """
80
  logger.info(f"Starting training for {epochs} epochs")
81
 
82
- try:
83
- for epoch in range(epochs):
84
- start_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
- # Training phase
87
- self.model.train()
88
- train_loss = 0
89
- all_predictions = []
90
- all_labels = []
 
91
 
92
- # Progress bar for training
93
- train_iterator = tqdm(self.train_loader, desc=f"Epoch {epoch+1}/{epochs} [Train]")
94
- for i, batch in enumerate(train_iterator):
95
- # Move batch to device
96
- input_ids = batch['input_ids'].to(self.device)
97
- attention_mask = batch['attention_mask'].to(self.device)
98
- token_type_ids = batch['token_type_ids'].to(self.device)
99
- labels = batch['label'].to(self.device)
100
-
101
- # Forward pass
102
- outputs = self.model(
103
- input_ids=input_ids,
104
- attention_mask=attention_mask,
105
- token_type_ids=token_type_ids
106
- )
107
-
108
- # Calculate loss
109
  loss = self.criterion(outputs, labels)
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- # Scale loss if using gradient accumulation
112
- if self.gradient_accumulation_steps > 1:
113
- loss = loss / self.gradient_accumulation_steps
114
-
115
- # Backward pass
116
- loss.backward()
117
-
118
- # Update weights if we've accumulated enough gradients
119
- if (i + 1) % self.gradient_accumulation_steps == 0:
120
- # Gradient clipping to prevent exploding gradients
121
- torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.max_grad_norm)
122
-
123
- self.optimizer.step()
124
- self.optimizer.zero_grad()
125
-
126
- train_loss += loss.item() * self.gradient_accumulation_steps
127
-
128
- # Get predictions for metrics
129
- if self.num_categories > 1:
130
- batch_size, total_classes = outputs.shape
131
- if total_classes % self.num_categories != 0:
132
- raise ValueError(f"Error: Number of total classes in the batch must of divisible by {self.num_categories}")
133
 
134
- classes_per_group = total_classes // self.num_categories
135
- # Group every classes_per_group values along dim=1
136
- reshaped = outputs.view(outputs.size(0), -1, classes_per_group) # shape: (batch, self., classes_per_group)
137
 
138
- # Argmax over each group of classes_per_group
139
- preds = reshaped.argmax(dim=-1)
140
- else:
141
- _, preds = torch.max(outputs, dim=1)
142
 
143
- all_predictions.extend(preds.cpu().tolist())
144
- all_labels.extend(labels.cpu().tolist())
145
-
146
- # Update progress bar with current loss
147
- train_iterator.set_postfix({'loss': f"{loss.item():.4f}"})
148
-
149
- # Calculate training metrics
150
- train_loss /= len(self.train_loader)
151
- train_acc = accuracy_score(all_labels, all_predictions)
152
- train_f1 = f1_score(all_labels, all_predictions, average='macro')
153
 
154
- # Validation phase
155
- val_loss, val_acc, val_f1, val_precision, val_recall = self.evaluate(self.val_loader, "Validation")
 
 
 
 
 
 
 
 
156
 
157
- # Log validation metrics
158
- logger.info(f"Validation - Loss: {val_loss:.4f}, Acc: {val_acc:.4f}, F1: {val_f1:.4f}, "
159
- f"Precision: {val_precision:.4f}, Recall: {val_recall:.4f}")
160
-
161
- # Adjust learning rate based on validation performance
162
- self.scheduler.step(val_f1)
163
-
164
- # Save best model
165
- if val_f1 > self.best_val_f1:
166
- self.best_val_f1 = val_f1
167
- self.best_model_state = self.model.state_dict().copy()
168
- torch.save(self.model.state_dict(), save_path)
169
- logger.info(f"New best model saved with validation F1: {val_f1:.4f}")
170
-
171
- # Print epoch summary
172
- epoch_time = time.time() - start_time
173
- logger.info(f"Epoch {epoch+1}/{epochs} - "
174
- f"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}, Train F1: {train_f1:.4f}, "
175
- f"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}, Val F1: {val_f1:.4f}, "
176
- f"Time: {epoch_time:.2f}s")
177
- except Exception as e:
178
- logger.error(f"Error during training: {e}")
179
- import traceback
180
- logger.error(traceback.format_exc())
181
 
 
 
 
 
 
 
 
182
  # Load best model for final evaluation
183
  if self.best_model_state is not None:
184
  self.model.load_state_dict(self.best_model_state)
@@ -219,7 +230,24 @@ class Trainer:
219
  )
220
 
221
  # Calculate loss
222
- loss = self.criterion(outputs, labels)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  eval_loss += loss.item()
224
 
225
  # Get predictions
 
79
  """
80
  logger.info(f"Starting training for {epochs} epochs")
81
 
82
+ for epoch in range(epochs):
83
+ start_time = time.time()
84
+
85
+ # Training phase
86
+ self.model.train()
87
+ train_loss = 0
88
+ all_predictions = []
89
+ all_labels = []
90
+
91
+ # Progress bar for training
92
+ train_iterator = tqdm(self.train_loader, desc=f"Epoch {epoch+1}/{epochs} [Train]")
93
+ for i, batch in enumerate(train_iterator):
94
+ # Move batch to device
95
+ input_ids = batch['input_ids'].to(self.device)
96
+ attention_mask = batch['attention_mask'].to(self.device)
97
+ token_type_ids = batch['token_type_ids'].to(self.device)
98
+ labels = batch['label'].to(self.device)
99
 
100
+ # Forward pass
101
+ outputs = self.model(
102
+ input_ids=input_ids,
103
+ attention_mask=attention_mask,
104
+ token_type_ids=token_type_ids
105
+ )
106
 
107
+ # Calculate loss
108
+
109
+ if self.num_categories > 1:
110
+ total_loss = 0
111
+ for i in range(self.num_categories):
112
+ start_idx = i * self.num_classes
113
+ end_idx = (i + 1) * self.num_classes
114
+ category_outputs = outputs[:, start_idx:end_idx] # Shape (batch, num_classes)
115
+ category_labels = labels[:, i] # Shape (batch)
116
+ # Ensure category_labels are in [0, self.num_classes - 1]
117
+ if category_labels.max() >= self.num_classes or category_labels.min() < 0:
118
+ print(f"ERROR: Category {i} labels out of range [0, {self.num_classes - 1}]: min={category_labels.min()}, max={category_labels.max()}")
119
+
120
+ total_loss += self.criterion(category_outputs, category_labels)
121
+
122
+ loss = total_loss / self.num_categories # Average loss
123
+ else:
124
  loss = self.criterion(outputs, labels)
125
+
126
+ # Scale loss if using gradient accumulation
127
+ if self.gradient_accumulation_steps > 1:
128
+ loss = loss / self.gradient_accumulation_steps
129
+
130
+ # Backward pass
131
+ loss.backward()
132
+
133
+ # Update weights if we've accumulated enough gradients
134
+ if (i + 1) % self.gradient_accumulation_steps == 0:
135
+ # Gradient clipping to prevent exploding gradients
136
+ torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.max_grad_norm)
137
 
138
+ self.optimizer.step()
139
+ self.optimizer.zero_grad()
140
+
141
+ train_loss += loss.item() * self.gradient_accumulation_steps
142
+
143
+ # Get predictions for metrics
144
+ if self.num_categories > 1:
145
+ batch_size, total_classes = outputs.shape
146
+ if total_classes % self.num_categories != 0:
147
+ raise ValueError(f"Error: Number of total classes in the batch must of divisible by {self.num_categories}")
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
+ classes_per_group = total_classes // self.num_categories
150
+ # Group every classes_per_group values along dim=1
151
+ reshaped = outputs.view(outputs.size(0), -1, classes_per_group) # shape: (batch, self., classes_per_group)
152
 
153
+ # Argmax over each group of classes_per_group
154
+ preds = reshaped.argmax(dim=-1)
155
+ else:
156
+ _, preds = torch.max(outputs, dim=1)
157
 
158
+ all_predictions.extend(preds.cpu().tolist())
159
+ all_labels.extend(labels.cpu().tolist())
 
 
 
 
 
 
 
 
160
 
161
+ # Update progress bar with current loss
162
+ train_iterator.set_postfix({'loss': f"{loss.item():.4f}"})
163
+
164
+ # Calculate training metrics
165
+ train_loss /= len(self.train_loader)
166
+ train_acc = accuracy_score(all_labels, all_predictions)
167
+ train_f1 = f1_score(all_labels, all_predictions, average='macro')
168
+
169
+ # Validation phase
170
+ val_loss, val_acc, val_f1, val_precision, val_recall = self.evaluate(self.val_loader, "Validation")
171
 
172
+ # Log validation metrics
173
+ logger.info(f"Validation - Loss: {val_loss:.4f}, Acc: {val_acc:.4f}, F1: {val_f1:.4f}, "
174
+ f"Precision: {val_precision:.4f}, Recall: {val_recall:.4f}")
175
+
176
+ # Adjust learning rate based on validation performance
177
+ self.scheduler.step(val_f1)
178
+
179
+ # Save best model
180
+ if val_f1 > self.best_val_f1:
181
+ self.best_val_f1 = val_f1
182
+ self.best_model_state = self.model.state_dict().copy()
183
+ torch.save(self.model.state_dict(), save_path)
184
+ logger.info(f"New best model saved with validation F1: {val_f1:.4f}")
 
 
 
 
 
 
 
 
 
 
 
185
 
186
+ # Print epoch summary
187
+ epoch_time = time.time() - start_time
188
+ logger.info(f"Epoch {epoch+1}/{epochs} - "
189
+ f"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}, Train F1: {train_f1:.4f}, "
190
+ f"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}, Val F1: {val_f1:.4f}, "
191
+ f"Time: {epoch_time:.2f}s")
192
+
193
  # Load best model for final evaluation
194
  if self.best_model_state is not None:
195
  self.model.load_state_dict(self.best_model_state)
 
230
  )
231
 
232
  # Calculate loss
233
+ if self.num_categories > 1:
234
+ total_loss = 0
235
+ for i in range(self.num_categories):
236
+ start_idx = i * self.num_classes
237
+ end_idx = (i + 1) * self.num_classes
238
+ category_outputs = outputs[:, start_idx:end_idx] # Shape (batch, num_classes)
239
+ category_labels = labels[:, i] # Shape (batch)
240
+
241
+ # Ensure category_labels are in [0, self.num_classes - 1]
242
+ if category_labels.max() >= self.num_classes or category_labels.min() < 0:
243
+ print(f"ERROR: Category {i} labels out of range [0, {self.num_classes - 1}]: min={category_labels.min()}, max={category_labels.max()}")
244
+
245
+ total_loss += self.criterion(category_outputs, category_labels)
246
+
247
+ loss = total_loss / self.num_categories # Average loss
248
+ else:
249
+ loss = self.criterion(outputs, labels)
250
+
251
  eval_loss += loss.item()
252
 
253
  # Get predictions