haukurpj commited on
Commit
27880a7
·
1 Parent(s): d2f797f

Format modeling

Browse files
Files changed (1) hide show
  1. modeling.py +87 -27
modeling.py CHANGED
@@ -36,7 +36,9 @@ class MultiLabelTokenClassificationHead(nn.Module):
36
  # (*, H) -> (*, C)
37
  self.cat_proj = nn.Linear(self.hidden_size, self.num_categories)
38
  # (*, H + C) -> (*, A)
39
- self.out_proj = nn.Linear(self.hidden_size + self.num_categories, self.num_labels)
 
 
40
 
41
  def forward(self, features: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
42
  """
@@ -119,9 +121,15 @@ class IceBertPosForTokenClassification(PreTrainedModel):
119
 
120
  # Create group attribute indices tensor: (G x max_group_size)
121
  # Instead of dict lookups, we can index directly: group_attr_indices[group_id, :]
122
- max_group_size = max(len(labels) for labels in schema.group_name_to_labels.values())
123
- self.group_attr_indices = torch.full((num_groups, max_group_size), -1, dtype=torch.long, device=device)
124
- self.group_sizes = torch.zeros(num_groups, dtype=torch.long, device=device) # (G,)
 
 
 
 
 
 
125
 
126
  for group_idx, group_name in enumerate(schema.group_names):
127
  group_labels = schema.group_name_to_labels[group_name]
@@ -179,7 +187,9 @@ class IceBertPosForTokenClassification(PreTrainedModel):
179
  cat_logits: Category logits (B x W x C)
180
  attr_logits: Attribute logits (B x W x A)
181
  """
182
- return_dict = return_dict if return_dict is not None else self.config.use_return_dict
 
 
183
 
184
  # Get RoBERTa outputs
185
  outputs = self.roberta(
@@ -197,7 +207,9 @@ class IceBertPosForTokenClassification(PreTrainedModel):
197
  hidden_states = outputs[0] # (B x L x H)
198
 
199
  # (B x L x H) -> (Wt x H)
200
- word_embeddings = self._aggregate_subword_tokens(hidden_states, word_mask, attention_mask)
 
 
201
 
202
  # (Wt x H) -> (Wt x C), (Wt x A)
203
  cat_logits, attr_logits = self.classifier(word_embeddings)
@@ -209,7 +221,10 @@ class IceBertPosForTokenClassification(PreTrainedModel):
209
  return cat_logits, attr_logits
210
 
211
  def _aggregate_subword_tokens(
212
- self, sequence_output: torch.Tensor, word_mask: torch.Tensor, attention_mask: torch.Tensor
 
 
 
213
  ) -> torch.Tensor:
214
  """
215
  Average subword tokens within each word to get word-level representations.
@@ -242,13 +257,17 @@ class IceBertPosForTokenClassification(PreTrainedModel):
242
 
243
  # Get word starts for this sequence
244
  seq_word_mask = word_mask[b, valid_mask] # (Lv,) - only valid positions
245
- word_starts = seq_word_mask.nonzero(as_tuple=True)[0] # (Ws,) - positions where words start
 
 
246
 
247
  if len(word_starts) == 0:
248
  continue
249
 
250
  # Assign each token to its word within this sequence
251
- seq_word_indices = torch.full((len(seq_word_mask),), -1, dtype=torch.long, device=device)
 
 
252
 
253
  for i, start_pos in enumerate(word_starts):
254
  # Find end position (next word start or end of sequence)
@@ -268,7 +287,10 @@ class IceBertPosForTokenClassification(PreTrainedModel):
268
  # This allows us to use scatter operations across the entire batch
269
  max_words_per_seq = word_mask.sum(dim=-1) # (B,) - words per sequence
270
  word_offset = torch.cat(
271
- [torch.zeros(1, device=device, dtype=torch.long), max_words_per_seq.cumsum(dim=0)[:-1]]
 
 
 
272
  ) # (B,) - cumulative word offsets
273
 
274
  # Add batch offsets to make global unique indices
@@ -283,7 +305,9 @@ class IceBertPosForTokenClassification(PreTrainedModel):
283
  # Only use tokens that belong to words (not padding and not before first word)
284
  valid_word_tokens = (flat_attention.bool()) & (flat_word_indices >= 0) # (B*L,)
285
  valid_output = flat_output[valid_word_tokens] # (valid_word_tokens x H)
286
- valid_word_indices = flat_word_indices[valid_word_tokens] # (valid_word_tokens,)
 
 
287
 
288
  total_words = max_words_per_seq.sum()
289
  if total_words == 0:
@@ -292,11 +316,17 @@ class IceBertPosForTokenClassification(PreTrainedModel):
292
  # Vectorized aggregation using scatter operations
293
  # Sum all token embeddings that belong to the same word
294
  word_sums = torch.zeros(total_words, hidden_size, device=device) # (Wt x H)
295
- word_sums.scatter_add_(0, valid_word_indices.unsqueeze(1).expand(-1, hidden_size), valid_output)
 
 
296
 
297
  # Count how many tokens belong to each word (for averaging)
298
  word_counts = torch.zeros(total_words, device=device) # (Wt,)
299
- word_counts.scatter_add_(0, valid_word_indices, torch.ones_like(valid_word_indices, dtype=torch.float))
 
 
 
 
300
 
301
  # Compute average: word_embedding = sum_of_tokens / count_of_tokens
302
  word_counts = torch.clamp(word_counts, min=1.0) # Prevent division by zero
@@ -304,7 +334,9 @@ class IceBertPosForTokenClassification(PreTrainedModel):
304
 
305
  return word_features
306
 
307
- def _reshape_to_batch_format(self, logits: torch.Tensor, nwords: torch.Tensor) -> torch.Tensor:
 
 
308
  """
309
  Reshape concatenated word predictions back to padded batch format.
310
 
@@ -357,7 +389,9 @@ class IceBertPosForTokenClassification(PreTrainedModel):
357
 
358
  # Debug logging to match fairseq model
359
  logger.debug(f"Encoded tokens: {input_ids}") # (L,)
360
- logger.debug(f"Decoded tokens: {tokenizer.convert_ids_to_tokens(input_ids.tolist())}")
 
 
361
  logger.debug(f"Word IDs: {word_ids}") # (L,)
362
  logger.debug(f"Word mask: {word_mask}")
363
 
@@ -365,7 +399,10 @@ class IceBertPosForTokenClassification(PreTrainedModel):
365
 
366
  @torch.no_grad()
367
  def predict_labels(
368
- self, input_ids: torch.Tensor, attention_mask: torch.Tensor, word_mask: torch.Tensor
 
 
 
369
  ) -> List[List[Tuple[str, List[str]]]]:
370
  """
371
  Predict POS labels for input sequences.
@@ -382,7 +419,9 @@ class IceBertPosForTokenClassification(PreTrainedModel):
382
  """
383
  # Time the forward pass
384
  start_time = time.perf_counter()
385
- cat_logits, attr_logits = self.forward(input_ids=input_ids, attention_mask=attention_mask, word_mask=word_mask)
 
 
386
  forward_time = time.perf_counter() - start_time
387
  logger.debug(f"Forward pass took {forward_time:.4f} seconds")
388
 
@@ -390,7 +429,9 @@ class IceBertPosForTokenClassification(PreTrainedModel):
390
  start_time = time.perf_counter()
391
  result = self._logits_to_labels(cat_logits, attr_logits, word_mask)
392
  logits_to_labels_time = time.perf_counter() - start_time
393
- logger.debug(f"Logits to labels conversion took {logits_to_labels_time:.4f} seconds")
 
 
394
 
395
  return result
396
 
@@ -414,19 +455,31 @@ class IceBertPosForTokenClassification(PreTrainedModel):
414
  all_word_masks = []
415
 
416
  for words in sentences:
417
- input_ids, attention_mask, word_mask = self.prepare_inputs(words, tokenizer, truncate)
 
 
418
  all_input_ids.append(input_ids)
419
  all_attention_masks.append(attention_mask)
420
  all_word_masks.append(word_mask)
421
 
422
  # Pad sequences to same length
423
- batch_input_ids = pad_sequence(all_input_ids, batch_first=True, padding_value=tokenizer.pad_token_id)
424
- batch_attention_mask = pad_sequence(all_attention_masks, batch_first=True, padding_value=0)
425
- batch_word_mask = pad_sequence(all_word_masks, batch_first=True, padding_value=0)
 
 
 
 
 
 
426
 
427
- return self.predict_labels(batch_input_ids, batch_attention_mask, batch_word_mask)
 
 
428
 
429
- def convert_labels_to_ifd(self, predictions: List[List[Tuple[str, List[str]]]]) -> List[List[str]]:
 
 
430
  """
431
  Convert model predictions to IFD format labels.
432
 
@@ -495,7 +548,10 @@ class IceBertPosForTokenClassification(PreTrainedModel):
495
  return word_mask
496
 
497
  def _logits_to_labels(
498
- self, cat_logits: torch.Tensor, attr_logits: torch.Tensor, word_mask: torch.Tensor
 
 
 
499
  ) -> List[List[Tuple[str, List[str]]]]:
500
  """
501
  Convert logits to human-readable labels using vectorized operations.
@@ -520,7 +576,9 @@ class IceBertPosForTokenClassification(PreTrainedModel):
520
  if nwords[b] > 0:
521
  batch_word_mask[b, : nwords[b]] = True
522
 
523
- valid_positions = batch_word_mask.flatten().nonzero(as_tuple=True)[0] # (total_words,)
 
 
524
  total_words = len(valid_positions)
525
 
526
  if total_words == 0:
@@ -607,7 +665,9 @@ class IceBertPosForTokenClassification(PreTrainedModel):
607
  attributes = []
608
  elif cat_name == "sl" and "act" in attributes:
609
  # Number and tense are not shown for sl act in mim format
610
- attributes = [attr for attr in attributes if attr not in ["1", "sing", "pres"]]
 
 
611
 
612
  seq_predictions.append((cat_name, attributes))
613
  word_counter += 1
 
36
  # (*, H) -> (*, C)
37
  self.cat_proj = nn.Linear(self.hidden_size, self.num_categories)
38
  # (*, H + C) -> (*, A)
39
+ self.out_proj = nn.Linear(
40
+ self.hidden_size + self.num_categories, self.num_labels
41
+ )
42
 
43
  def forward(self, features: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
44
  """
 
121
 
122
  # Create group attribute indices tensor: (G x max_group_size)
123
  # Instead of dict lookups, we can index directly: group_attr_indices[group_id, :]
124
+ max_group_size = max(
125
+ len(labels) for labels in schema.group_name_to_labels.values()
126
+ )
127
+ self.group_attr_indices = torch.full(
128
+ (num_groups, max_group_size), -1, dtype=torch.long, device=device
129
+ )
130
+ self.group_sizes = torch.zeros(
131
+ num_groups, dtype=torch.long, device=device
132
+ ) # (G,)
133
 
134
  for group_idx, group_name in enumerate(schema.group_names):
135
  group_labels = schema.group_name_to_labels[group_name]
 
187
  cat_logits: Category logits (B x W x C)
188
  attr_logits: Attribute logits (B x W x A)
189
  """
190
+ return_dict = (
191
+ return_dict if return_dict is not None else self.config.use_return_dict
192
+ )
193
 
194
  # Get RoBERTa outputs
195
  outputs = self.roberta(
 
207
  hidden_states = outputs[0] # (B x L x H)
208
 
209
  # (B x L x H) -> (Wt x H)
210
+ word_embeddings = self._aggregate_subword_tokens(
211
+ hidden_states, word_mask, attention_mask
212
+ )
213
 
214
  # (Wt x H) -> (Wt x C), (Wt x A)
215
  cat_logits, attr_logits = self.classifier(word_embeddings)
 
221
  return cat_logits, attr_logits
222
 
223
  def _aggregate_subword_tokens(
224
+ self,
225
+ sequence_output: torch.Tensor,
226
+ word_mask: torch.Tensor,
227
+ attention_mask: torch.Tensor,
228
  ) -> torch.Tensor:
229
  """
230
  Average subword tokens within each word to get word-level representations.
 
257
 
258
  # Get word starts for this sequence
259
  seq_word_mask = word_mask[b, valid_mask] # (Lv,) - only valid positions
260
+ word_starts = seq_word_mask.nonzero(as_tuple=True)[
261
+ 0
262
+ ] # (Ws,) - positions where words start
263
 
264
  if len(word_starts) == 0:
265
  continue
266
 
267
  # Assign each token to its word within this sequence
268
+ seq_word_indices = torch.full(
269
+ (len(seq_word_mask),), -1, dtype=torch.long, device=device
270
+ )
271
 
272
  for i, start_pos in enumerate(word_starts):
273
  # Find end position (next word start or end of sequence)
 
287
  # This allows us to use scatter operations across the entire batch
288
  max_words_per_seq = word_mask.sum(dim=-1) # (B,) - words per sequence
289
  word_offset = torch.cat(
290
+ [
291
+ torch.zeros(1, device=device, dtype=torch.long),
292
+ max_words_per_seq.cumsum(dim=0)[:-1],
293
+ ]
294
  ) # (B,) - cumulative word offsets
295
 
296
  # Add batch offsets to make global unique indices
 
305
  # Only use tokens that belong to words (not padding and not before first word)
306
  valid_word_tokens = (flat_attention.bool()) & (flat_word_indices >= 0) # (B*L,)
307
  valid_output = flat_output[valid_word_tokens] # (valid_word_tokens x H)
308
+ valid_word_indices = flat_word_indices[
309
+ valid_word_tokens
310
+ ] # (valid_word_tokens,)
311
 
312
  total_words = max_words_per_seq.sum()
313
  if total_words == 0:
 
316
  # Vectorized aggregation using scatter operations
317
  # Sum all token embeddings that belong to the same word
318
  word_sums = torch.zeros(total_words, hidden_size, device=device) # (Wt x H)
319
+ word_sums.scatter_add_(
320
+ 0, valid_word_indices.unsqueeze(1).expand(-1, hidden_size), valid_output
321
+ )
322
 
323
  # Count how many tokens belong to each word (for averaging)
324
  word_counts = torch.zeros(total_words, device=device) # (Wt,)
325
+ word_counts.scatter_add_(
326
+ 0,
327
+ valid_word_indices,
328
+ torch.ones_like(valid_word_indices, dtype=torch.float),
329
+ )
330
 
331
  # Compute average: word_embedding = sum_of_tokens / count_of_tokens
332
  word_counts = torch.clamp(word_counts, min=1.0) # Prevent division by zero
 
334
 
335
  return word_features
336
 
337
+ def _reshape_to_batch_format(
338
+ self, logits: torch.Tensor, nwords: torch.Tensor
339
+ ) -> torch.Tensor:
340
  """
341
  Reshape concatenated word predictions back to padded batch format.
342
 
 
389
 
390
  # Debug logging to match fairseq model
391
  logger.debug(f"Encoded tokens: {input_ids}") # (L,)
392
+ logger.debug(
393
+ f"Decoded tokens: {tokenizer.convert_ids_to_tokens(input_ids.tolist())}"
394
+ )
395
  logger.debug(f"Word IDs: {word_ids}") # (L,)
396
  logger.debug(f"Word mask: {word_mask}")
397
 
 
399
 
400
  @torch.no_grad()
401
  def predict_labels(
402
+ self,
403
+ input_ids: torch.Tensor,
404
+ attention_mask: torch.Tensor,
405
+ word_mask: torch.Tensor,
406
  ) -> List[List[Tuple[str, List[str]]]]:
407
  """
408
  Predict POS labels for input sequences.
 
419
  """
420
  # Time the forward pass
421
  start_time = time.perf_counter()
422
+ cat_logits, attr_logits = self.forward(
423
+ input_ids=input_ids, attention_mask=attention_mask, word_mask=word_mask
424
+ )
425
  forward_time = time.perf_counter() - start_time
426
  logger.debug(f"Forward pass took {forward_time:.4f} seconds")
427
 
 
429
  start_time = time.perf_counter()
430
  result = self._logits_to_labels(cat_logits, attr_logits, word_mask)
431
  logits_to_labels_time = time.perf_counter() - start_time
432
+ logger.debug(
433
+ f"Logits to labels conversion took {logits_to_labels_time:.4f} seconds"
434
+ )
435
 
436
  return result
437
 
 
455
  all_word_masks = []
456
 
457
  for words in sentences:
458
+ input_ids, attention_mask, word_mask = self.prepare_inputs(
459
+ words, tokenizer, truncate
460
+ )
461
  all_input_ids.append(input_ids)
462
  all_attention_masks.append(attention_mask)
463
  all_word_masks.append(word_mask)
464
 
465
  # Pad sequences to same length
466
+ batch_input_ids = pad_sequence(
467
+ all_input_ids, batch_first=True, padding_value=tokenizer.pad_token_id
468
+ )
469
+ batch_attention_mask = pad_sequence(
470
+ all_attention_masks, batch_first=True, padding_value=0
471
+ )
472
+ batch_word_mask = pad_sequence(
473
+ all_word_masks, batch_first=True, padding_value=0
474
+ )
475
 
476
+ return self.predict_labels(
477
+ batch_input_ids, batch_attention_mask, batch_word_mask
478
+ )
479
 
480
+ def convert_labels_to_ifd(
481
+ self, predictions: List[List[Tuple[str, List[str]]]]
482
+ ) -> List[List[str]]:
483
  """
484
  Convert model predictions to IFD format labels.
485
 
 
548
  return word_mask
549
 
550
  def _logits_to_labels(
551
+ self,
552
+ cat_logits: torch.Tensor,
553
+ attr_logits: torch.Tensor,
554
+ word_mask: torch.Tensor,
555
  ) -> List[List[Tuple[str, List[str]]]]:
556
  """
557
  Convert logits to human-readable labels using vectorized operations.
 
576
  if nwords[b] > 0:
577
  batch_word_mask[b, : nwords[b]] = True
578
 
579
+ valid_positions = batch_word_mask.flatten().nonzero(as_tuple=True)[
580
+ 0
581
+ ] # (total_words,)
582
  total_words = len(valid_positions)
583
 
584
  if total_words == 0:
 
665
  attributes = []
666
  elif cat_name == "sl" and "act" in attributes:
667
  # Number and tense are not shown for sl act in mim format
668
+ attributes = [
669
+ attr for attr in attributes if attr not in ["1", "sing", "pres"]
670
+ ]
671
 
672
  seq_predictions.append((cat_name, attributes))
673
  word_counter += 1