Instructions to use mideind/IceBERT-PoS with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use mideind/IceBERT-PoS with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="mideind/IceBERT-PoS", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("mideind/IceBERT-PoS", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Format modeling
Browse files- 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(
|
|
|
|
|
|
|
| 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(
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 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,
|
|
|
|
|
|
|
|
|
|
| 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)[
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 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 |
-
[
|
|
|
|
|
|
|
|
|
|
| 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[
|
|
|
|
|
|
|
| 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_(
|
|
|
|
|
|
|
| 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_(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 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,
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 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(
|
| 424 |
-
|
| 425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
|
| 427 |
-
return self.predict_labels(
|
|
|
|
|
|
|
| 428 |
|
| 429 |
-
def convert_labels_to_ifd(
|
|
|
|
|
|
|
| 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,
|
|
|
|
|
|
|
|
|
|
| 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)[
|
|
|
|
|
|
|
| 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 = [
|
|
|
|
|
|
|
| 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
|