noch inator commited on
Update ThoughtVectors.py
Browse filesAdded improvements, including more error handling, proper loss calculation/application, and more
- ThoughtVectors.py +74 -68
ThoughtVectors.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
# thought_vectors.py
|
| 2 |
import random
|
| 3 |
import math
|
| 4 |
import torch
|
|
@@ -6,7 +5,7 @@ import torch.nn as nn
|
|
| 6 |
import torch.optim as optim
|
| 7 |
import sentencepiece as spm
|
| 8 |
from torch.utils.data import Dataset, DataLoader
|
| 9 |
-
from torch.
|
| 10 |
from typing import List, Union, Generator, Sequence, Optional
|
| 11 |
import os
|
| 12 |
import csv
|
|
@@ -48,8 +47,7 @@ class PositionalEncoding(nn.Module):
|
|
| 48 |
(-math.log(10000.0) / d_model))
|
| 49 |
|
| 50 |
pe[:, 0::2] = torch.sin(position * div_term)
|
| 51 |
-
pe[:, 1::2] = torch.cos(position * div_term[:d_model // 2 +
|
| 52 |
-
|
| 53 |
pe = pe.unsqueeze(0)
|
| 54 |
self.register_buffer('pe', pe)
|
| 55 |
else:
|
|
@@ -107,7 +105,8 @@ class ThoughtEncoder(nn.Module):
|
|
| 107 |
nhead: int = 8, num_layers: int = 2, dropout: float = 0.1, pad_id: int = 0,
|
| 108 |
termination_threshold: float = 0.75, max_len: int = 5000):
|
| 109 |
super().__init__()
|
| 110 |
-
|
|
|
|
| 111 |
self.d_model = d_model
|
| 112 |
self.max_thoughts = max_thoughts
|
| 113 |
self.pad_id = pad_id
|
|
@@ -164,7 +163,7 @@ class ThoughtEncoder(nn.Module):
|
|
| 164 |
|
| 165 |
thought_vectors_list.append(next_thought)
|
| 166 |
finished = finished | (termination_score > self.termination_threshold)
|
| 167 |
-
if finished.all():
|
| 168 |
break
|
| 169 |
|
| 170 |
return torch.stack(thought_vectors_list, dim=1)
|
|
@@ -202,6 +201,8 @@ class ThoughtDecoder(nn.Module):
|
|
| 202 |
Returns:
|
| 203 |
torch.Tensor: Mask tensor, shape (sz, sz).
|
| 204 |
"""
|
|
|
|
|
|
|
| 205 |
mask = (torch.triu(torch.ones(sz, sz, device=device)) == 1).transpose(0, 1)
|
| 206 |
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
|
| 207 |
return mask
|
|
@@ -302,43 +303,40 @@ class LazyList(Sequence):
|
|
| 302 |
else:
|
| 303 |
self._length = 4
|
| 304 |
return self._length
|
| 305 |
-
|
| 306 |
-
def __getitem__(self, index: int) -> List[str]:
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
"""
|
| 315 |
if self.data_list is not None:
|
| 316 |
-
# For in-memory list, pick a random row (faster than iterating)
|
| 317 |
return random.choice(self.data_list)
|
| 318 |
-
|
| 319 |
elif self.filepath and os.path.exists(self.filepath):
|
| 320 |
self._open_file()
|
| 321 |
try:
|
| 322 |
while True:
|
| 323 |
row = next(self._reader)
|
| 324 |
-
if row and random.random() <= self.sample_prob:
|
| 325 |
return row
|
| 326 |
except StopIteration:
|
| 327 |
-
# Reset file pointer to start if we reach the end
|
| 328 |
self._file_handle.seek(0)
|
| 329 |
self._reader = csv.reader(self._file_handle)
|
| 330 |
-
row = next(self._reader)
|
| 331 |
-
if row
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
|
|
|
|
|
|
| 336 |
["Hello world", "AI is cool"],
|
| 337 |
["This is a test", "Another sentence"],
|
| 338 |
["Python is fun", "Coding rocks"],
|
| 339 |
["Short sentence", "Quick test"],
|
| 340 |
]
|
| 341 |
-
return random.choice(dummy_data)
|
| 342 |
|
| 343 |
def __del__(self):
|
| 344 |
"""Close the file handle when the object is destroyed."""
|
|
@@ -441,6 +439,7 @@ class ThoughtVectors:
|
|
| 441 |
test_lazy_data = LazyList(test_data) if test_data is not None else None
|
| 442 |
|
| 443 |
if model and os.path.exists(model):
|
|
|
|
| 444 |
checkpoint = torch.load(model, map_location=self.device)
|
| 445 |
self.sp.Load(checkpoint['spm_model_path'])
|
| 446 |
self.vocab_size = self.sp.GetPieceSize()
|
|
@@ -457,6 +456,7 @@ class ThoughtVectors:
|
|
| 457 |
self.decoder.load_state_dict(checkpoint['decoder_state_dict'])
|
| 458 |
print(f"Loaded pre-existing model from {model}")
|
| 459 |
else:
|
|
|
|
| 460 |
with tempfile.NamedTemporaryFile(mode="w", encoding="utf8", delete=False) as temp_file:
|
| 461 |
for group in train_lazy_data:
|
| 462 |
for sentence in group:
|
|
@@ -466,6 +466,8 @@ class ThoughtVectors:
|
|
| 466 |
temp_dir = tempfile.mkdtemp()
|
| 467 |
temp_spm_prefix = os.path.join(temp_dir, "spm")
|
| 468 |
try:
|
|
|
|
|
|
|
| 469 |
spm.SentencePieceTrainer.Train(
|
| 470 |
input=temp_dataset_path,
|
| 471 |
model_prefix=temp_spm_prefix,
|
|
@@ -499,56 +501,54 @@ class ThoughtVectors:
|
|
| 499 |
lr=learning_rate, weight_decay=weight_decay)
|
| 500 |
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_epochs)
|
| 501 |
criterion = nn.CrossEntropyLoss(ignore_index=self.sp.pad_id())
|
| 502 |
-
scaler = GradScaler()
|
| 503 |
|
| 504 |
best_val_loss = float('inf')
|
| 505 |
patience_counter = 0
|
| 506 |
|
| 507 |
print("Beginning training")
|
| 508 |
for epoch in range(num_epochs):
|
| 509 |
-
self.encoder.train()
|
| 510 |
-
self.decoder.train()
|
| 511 |
total_train_loss = 0.0
|
| 512 |
-
|
| 513 |
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
random.shuffle(train_data_list)
|
| 517 |
-
num_train_batches = (len(train_data_list) + batch_size - 1) // batch_size
|
| 518 |
-
|
| 519 |
-
for i in range(0, len(train_data_list), batch_size):
|
| 520 |
-
raw_batch = train_data_list[i:i + batch_size]
|
| 521 |
-
# Tokenize each group in the batch
|
| 522 |
tokenized_batch = [
|
| 523 |
[[self.sp.bos_id()] + self.sp.EncodeAsIds(s) + [self.sp.eos_id()] for s in group]
|
| 524 |
for group in raw_batch
|
| 525 |
]
|
| 526 |
-
group_batch = group_collate_fn(tokenized_batch)
|
| 527 |
|
| 528 |
batch_loss = 0.0
|
| 529 |
-
with autocast():
|
| 530 |
for group_tensor in group_batch:
|
| 531 |
if group_tensor.shape[1] <= self.max_len and group_tensor.numel() > 0:
|
| 532 |
group_tensor = group_tensor.to(self.device)
|
| 533 |
force_single_vector = random.random() < single_vector_prob
|
| 534 |
thought_vectors = self.encoder(group_tensor, force_single_vector)
|
| 535 |
output_logits = self.decoder(thought_vectors, group_tensor[:, :-1])
|
|
|
|
|
|
|
|
|
|
| 536 |
loss = criterion(output_logits.reshape(-1, self.vocab_size),
|
| 537 |
group_tensor[:, 1:].reshape(-1))
|
|
|
|
| 538 |
if not force_single_vector:
|
| 539 |
loss += length_penalty * thought_vectors.shape[1]
|
| 540 |
batch_loss += loss / accum_steps
|
| 541 |
|
| 542 |
scaler.scale(batch_loss).backward()
|
|
|
|
| 543 |
if (i // batch_size + 1) % accum_steps == 0:
|
| 544 |
scaler.step(optimizer)
|
| 545 |
scaler.update()
|
| 546 |
optimizer.zero_grad()
|
| 547 |
torch.cuda.empty_cache()
|
| 548 |
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
|
|
|
| 552 |
|
| 553 |
avg_train_loss = total_train_loss / num_train_batches if num_train_batches > 0 else float('inf')
|
| 554 |
|
|
@@ -557,13 +557,12 @@ class ThoughtVectors:
|
|
| 557 |
self.encoder.eval()
|
| 558 |
self.decoder.eval()
|
| 559 |
total_val_loss = 0.0
|
| 560 |
-
|
| 561 |
-
|
| 562 |
|
| 563 |
with torch.no_grad():
|
| 564 |
-
for i in range(0, len(
|
| 565 |
-
raw_batch =
|
| 566 |
-
# Tokenize each group in the batch
|
| 567 |
tokenized_batch = [
|
| 568 |
[[self.sp.bos_id()] + self.sp.EncodeAsIds(s) + [self.sp.eos_id()] for s in group]
|
| 569 |
for group in raw_batch
|
|
@@ -578,14 +577,17 @@ class ThoughtVectors:
|
|
| 578 |
output_logits = self.decoder(thought_vectors, group_tensor[:, :-1])
|
| 579 |
val_loss = criterion(output_logits.reshape(-1, self.vocab_size),
|
| 580 |
group_tensor[:, 1:].reshape(-1))
|
|
|
|
| 581 |
batch_val_loss += val_loss.item()
|
| 582 |
total_val_loss += batch_val_loss
|
| 583 |
-
|
|
|
|
|
|
|
| 584 |
avg_val_loss = total_val_loss / num_val_batches if num_val_batches > 0 else float('inf')
|
| 585 |
else:
|
| 586 |
avg_val_loss = avg_train_loss
|
| 587 |
|
| 588 |
-
print(f"Epoch {epoch + 1}/{num_epochs} - Train Loss: {avg_train_loss:.4f} - Val Loss: {avg_val_loss:.4f}")
|
| 589 |
|
| 590 |
if avg_val_loss < best_val_loss:
|
| 591 |
best_val_loss = avg_val_loss
|
|
@@ -626,14 +628,14 @@ class ThoughtVectors:
|
|
| 626 |
|
| 627 |
return self.encoder(input_tokens, force_single_vector)
|
| 628 |
|
| 629 |
-
def decode(self, thought_vectors: torch.Tensor, max_length: int = 50, beam_width:
|
| 630 |
temperature: float = 1.0) -> List[str]:
|
| 631 |
"""Decodes thought vectors into text sequences.
|
| 632 |
|
| 633 |
Args:
|
| 634 |
thought_vectors (torch.Tensor): Thought vectors, shape (batch_size, num_thoughts, d_model).
|
| 635 |
max_length (int, optional): Maximum length of generated sequences. Defaults to 50.
|
| 636 |
-
beam_width (Optional[int], optional): Beam width for beam search; if
|
| 637 |
decoding. Defaults to 5.
|
| 638 |
temperature (float, optional): Temperature for softmax sampling in greedy decoding.
|
| 639 |
Defaults to 1.0.
|
|
@@ -647,13 +649,14 @@ class ThoughtVectors:
|
|
| 647 |
thought_vectors = thought_vectors.to(self.device)
|
| 648 |
batch_size = thought_vectors.size(0)
|
| 649 |
|
| 650 |
-
if beam_width
|
| 651 |
return self._beam_search_decode(thought_vectors, max_length, beam_width)
|
| 652 |
|
| 653 |
target_tokens = torch.full((batch_size, 1), self.sp.bos_id(), dtype=torch.long, device=self.device)
|
| 654 |
for _ in range(max_length - 1):
|
| 655 |
logits = self.decoder(thought_vectors, target_tokens)
|
| 656 |
logits = logits[:, -1, :] / temperature
|
|
|
|
| 657 |
next_token = logits.softmax(dim=-1).multinomial(1)
|
| 658 |
target_tokens = torch.cat([target_tokens, next_token], dim=1)
|
| 659 |
if (next_token == self.sp.eos_id()).all():
|
|
@@ -702,7 +705,8 @@ class ThoughtVectors:
|
|
| 702 |
results = []
|
| 703 |
for i in range(batch_size):
|
| 704 |
combined = sorted(finished[i] + beams[i], key=lambda x: x[0])
|
| 705 |
-
results.append(self.sp.DecodeIds(combined[0][1].squeeze(0).tolist()) if combined else
|
|
|
|
| 706 |
|
| 707 |
return results
|
| 708 |
|
|
@@ -756,10 +760,11 @@ class ThoughtVectors:
|
|
| 756 |
ThoughtVectors: Loaded model instance.
|
| 757 |
"""
|
| 758 |
translator = cls()
|
|
|
|
| 759 |
try:
|
| 760 |
with tarfile.open(path, "r") as tar:
|
| 761 |
tar.extractall()
|
| 762 |
-
checkpoint = torch.load("model.pth")
|
| 763 |
translator.sp = spm.SentencePieceProcessor()
|
| 764 |
if not translator.sp.Load("spm.model"):
|
| 765 |
raise RuntimeError("Failed to load SentencePiece model.")
|
|
@@ -778,6 +783,7 @@ class ThoughtVectors:
|
|
| 778 |
translator.dropout = checkpoint['dropout']
|
| 779 |
translator.max_len = checkpoint['max_len']
|
| 780 |
translator.termination_threshold = checkpoint['termination_threshold']
|
|
|
|
| 781 |
|
| 782 |
translator.encoder = ThoughtEncoder(
|
| 783 |
vocab_size=translator.vocab_size, d_model=translator.d_model, max_thoughts=translator.max_thoughts,
|
|
@@ -796,16 +802,16 @@ class ThoughtVectors:
|
|
| 796 |
|
| 797 |
|
| 798 |
if __name__ == "__main__":
|
| 799 |
-
csv.field_size_limit(8 ** 8)
|
| 800 |
tv = ThoughtVectors()
|
| 801 |
tv.train(
|
| 802 |
-
group_data="
|
|
|
|
| 803 |
num_epochs=1000,
|
| 804 |
-
batch_size=
|
| 805 |
-
accum_steps=
|
| 806 |
-
learning_rate=
|
| 807 |
weight_decay=2e-5,
|
| 808 |
-
length_penalty=0.
|
| 809 |
single_vector_prob=0.1,
|
| 810 |
save_path="thought_vectors_prototype.tar",
|
| 811 |
spm_model_prefix="spm",
|
|
@@ -813,18 +819,18 @@ if __name__ == "__main__":
|
|
| 813 |
d_model=512,
|
| 814 |
encoder_nhead=8,
|
| 815 |
decoder_nhead=8,
|
| 816 |
-
encoder_layers=
|
| 817 |
-
decoder_layers=
|
| 818 |
-
max_thoughts=
|
| 819 |
dropout=0.1,
|
| 820 |
-
max_len=
|
| 821 |
termination_threshold=0.8,
|
| 822 |
patience=5
|
| 823 |
)
|
| 824 |
loaded_tv = ThoughtVectors.load("thought_vectors_prototype.tar")
|
| 825 |
thought_vectors = loaded_tv.encode("AI is smart")
|
| 826 |
-
generated_text_greedy = loaded_tv.decode(thought_vectors, temperature=0.7)
|
| 827 |
-
generated_text_beam = loaded_tv.decode(thought_vectors, beam_width=
|
| 828 |
print(f"Greedy decoding: {generated_text_greedy}")
|
| 829 |
print(f"Beam search decoding: {generated_text_beam}")
|
| 830 |
|
|
|
|
|
|
|
| 1 |
import random
|
| 2 |
import math
|
| 3 |
import torch
|
|
|
|
| 5 |
import torch.optim as optim
|
| 6 |
import sentencepiece as spm
|
| 7 |
from torch.utils.data import Dataset, DataLoader
|
| 8 |
+
from torch.amp import autocast, GradScaler
|
| 9 |
from typing import List, Union, Generator, Sequence, Optional
|
| 10 |
import os
|
| 11 |
import csv
|
|
|
|
| 47 |
(-math.log(10000.0) / d_model))
|
| 48 |
|
| 49 |
pe[:, 0::2] = torch.sin(position * div_term)
|
| 50 |
+
pe[:, 1::2] = torch.cos(position * div_term[:self.d_model // 2 + (self.d_model % 2)])
|
|
|
|
| 51 |
pe = pe.unsqueeze(0)
|
| 52 |
self.register_buffer('pe', pe)
|
| 53 |
else:
|
|
|
|
| 105 |
nhead: int = 8, num_layers: int = 2, dropout: float = 0.1, pad_id: int = 0,
|
| 106 |
termination_threshold: float = 0.75, max_len: int = 5000):
|
| 107 |
super().__init__()
|
| 108 |
+
if nhead % 2 != 0:
|
| 109 |
+
raise ValueError(f"nhead must be even for thought_attention, got {nhead}")
|
| 110 |
self.d_model = d_model
|
| 111 |
self.max_thoughts = max_thoughts
|
| 112 |
self.pad_id = pad_id
|
|
|
|
| 163 |
|
| 164 |
thought_vectors_list.append(next_thought)
|
| 165 |
finished = finished | (termination_score > self.termination_threshold)
|
| 166 |
+
if finished.all() and not force_single_vector:
|
| 167 |
break
|
| 168 |
|
| 169 |
return torch.stack(thought_vectors_list, dim=1)
|
|
|
|
| 201 |
Returns:
|
| 202 |
torch.Tensor: Mask tensor, shape (sz, sz).
|
| 203 |
"""
|
| 204 |
+
if self.positional_encoding.max_len > 0 and sz > self.positional_encoding.max_len:
|
| 205 |
+
raise ValueError(f"Sequence length {sz} exceeds max_len {self.positional_encoding.max_len}")
|
| 206 |
mask = (torch.triu(torch.ones(sz, sz, device=device)) == 1).transpose(0, 1)
|
| 207 |
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
|
| 208 |
return mask
|
|
|
|
| 303 |
else:
|
| 304 |
self._length = 4
|
| 305 |
return self._length
|
| 306 |
+
|
| 307 |
+
def __getitem__(self, index: Union[int, slice]) -> Union[List[str], List[List[str]]]:
|
| 308 |
+
if isinstance(index, slice):
|
| 309 |
+
start, stop, step = index.indices(len(self))
|
| 310 |
+
count = (stop - start) // (step or 1)
|
| 311 |
+
return [self._get_single_item() for _ in range(max(0, count))]
|
| 312 |
+
return self._get_single_item()
|
| 313 |
+
|
| 314 |
+
def _get_single_item(self) -> List[str]:
|
|
|
|
| 315 |
if self.data_list is not None:
|
|
|
|
| 316 |
return random.choice(self.data_list)
|
|
|
|
| 317 |
elif self.filepath and os.path.exists(self.filepath):
|
| 318 |
self._open_file()
|
| 319 |
try:
|
| 320 |
while True:
|
| 321 |
row = next(self._reader)
|
| 322 |
+
if row and random.random() <= self.sample_prob:
|
| 323 |
return row
|
| 324 |
except StopIteration:
|
|
|
|
| 325 |
self._file_handle.seek(0)
|
| 326 |
self._reader = csv.reader(self._file_handle)
|
| 327 |
+
row = next(self._reader)
|
| 328 |
+
return row if row else random.choice(self._dummy_data())
|
| 329 |
+
print("WARN: file error, file may be empty, missing, or inaccessible. Dummy data returned") # change to raise in release
|
| 330 |
+
return random.choice(self._dummy_data()) # remove in release
|
| 331 |
+
|
| 332 |
+
# remove in release
|
| 333 |
+
def _dummy_data(self) -> List[List[str]]:
|
| 334 |
+
return [
|
| 335 |
["Hello world", "AI is cool"],
|
| 336 |
["This is a test", "Another sentence"],
|
| 337 |
["Python is fun", "Coding rocks"],
|
| 338 |
["Short sentence", "Quick test"],
|
| 339 |
]
|
|
|
|
| 340 |
|
| 341 |
def __del__(self):
|
| 342 |
"""Close the file handle when the object is destroyed."""
|
|
|
|
| 439 |
test_lazy_data = LazyList(test_data) if test_data is not None else None
|
| 440 |
|
| 441 |
if model and os.path.exists(model):
|
| 442 |
+
print("loading model...")
|
| 443 |
checkpoint = torch.load(model, map_location=self.device)
|
| 444 |
self.sp.Load(checkpoint['spm_model_path'])
|
| 445 |
self.vocab_size = self.sp.GetPieceSize()
|
|
|
|
| 456 |
self.decoder.load_state_dict(checkpoint['decoder_state_dict'])
|
| 457 |
print(f"Loaded pre-existing model from {model}")
|
| 458 |
else:
|
| 459 |
+
print("building models")
|
| 460 |
with tempfile.NamedTemporaryFile(mode="w", encoding="utf8", delete=False) as temp_file:
|
| 461 |
for group in train_lazy_data:
|
| 462 |
for sentence in group:
|
|
|
|
| 466 |
temp_dir = tempfile.mkdtemp()
|
| 467 |
temp_spm_prefix = os.path.join(temp_dir, "spm")
|
| 468 |
try:
|
| 469 |
+
print("training sentence piece (could take a while on massive datasets)")
|
| 470 |
+
print("Also you will get some log spamming from it cause it won't shut up")
|
| 471 |
spm.SentencePieceTrainer.Train(
|
| 472 |
input=temp_dataset_path,
|
| 473 |
model_prefix=temp_spm_prefix,
|
|
|
|
| 501 |
lr=learning_rate, weight_decay=weight_decay)
|
| 502 |
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_epochs)
|
| 503 |
criterion = nn.CrossEntropyLoss(ignore_index=self.sp.pad_id())
|
| 504 |
+
scaler = GradScaler('cuda')
|
| 505 |
|
| 506 |
best_val_loss = float('inf')
|
| 507 |
patience_counter = 0
|
| 508 |
|
| 509 |
print("Beginning training")
|
| 510 |
for epoch in range(num_epochs):
|
|
|
|
|
|
|
| 511 |
total_train_loss = 0.0
|
| 512 |
+
num_train_batches = (len(train_lazy_data) + batch_size - 1) // batch_size
|
| 513 |
|
| 514 |
+
for i in range(0, len(train_lazy_data), batch_size):
|
| 515 |
+
raw_batch = train_lazy_data[i:i + batch_size]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
tokenized_batch = [
|
| 517 |
[[self.sp.bos_id()] + self.sp.EncodeAsIds(s) + [self.sp.eos_id()] for s in group]
|
| 518 |
for group in raw_batch
|
| 519 |
]
|
| 520 |
+
group_batch = group_collate_fn(tokenized_batch)
|
| 521 |
|
| 522 |
batch_loss = 0.0
|
| 523 |
+
with autocast('cuda'):
|
| 524 |
for group_tensor in group_batch:
|
| 525 |
if group_tensor.shape[1] <= self.max_len and group_tensor.numel() > 0:
|
| 526 |
group_tensor = group_tensor.to(self.device)
|
| 527 |
force_single_vector = random.random() < single_vector_prob
|
| 528 |
thought_vectors = self.encoder(group_tensor, force_single_vector)
|
| 529 |
output_logits = self.decoder(thought_vectors, group_tensor[:, :-1])
|
| 530 |
+
|
| 531 |
+
# Masked loss
|
| 532 |
+
mask = (group_tensor[:, 1:] != self.sp.pad_id()).float()
|
| 533 |
loss = criterion(output_logits.reshape(-1, self.vocab_size),
|
| 534 |
group_tensor[:, 1:].reshape(-1))
|
| 535 |
+
loss = (loss * mask.reshape(-1)).sum() / mask.sum().clamp(min=1.0)
|
| 536 |
if not force_single_vector:
|
| 537 |
loss += length_penalty * thought_vectors.shape[1]
|
| 538 |
batch_loss += loss / accum_steps
|
| 539 |
|
| 540 |
scaler.scale(batch_loss).backward()
|
| 541 |
+
total_train_loss += batch_loss.item() * accum_steps
|
| 542 |
if (i // batch_size + 1) % accum_steps == 0:
|
| 543 |
scaler.step(optimizer)
|
| 544 |
scaler.update()
|
| 545 |
optimizer.zero_grad()
|
| 546 |
torch.cuda.empty_cache()
|
| 547 |
|
| 548 |
+
print(f"Batch {i // batch_size + 1}/{num_train_batches} - Loss: {batch_loss.item():.4f}")
|
| 549 |
+
# Debugging
|
| 550 |
+
print(f" Input: {self.sp.DecodeIds(group_tensor[0].tolist())}")
|
| 551 |
+
print(f"Output: {self.sp.DecodeIds(output_logits.argmax(-1)[0].tolist())}\n\n")
|
| 552 |
|
| 553 |
avg_train_loss = total_train_loss / num_train_batches if num_train_batches > 0 else float('inf')
|
| 554 |
|
|
|
|
| 557 |
self.encoder.eval()
|
| 558 |
self.decoder.eval()
|
| 559 |
total_val_loss = 0.0
|
| 560 |
+
num_val_batches = (len(test_lazy_data) + batch_size - 1) // batch_size
|
| 561 |
+
print(f"Starting validation with {num_val_batches} batches")
|
| 562 |
|
| 563 |
with torch.no_grad():
|
| 564 |
+
for i in range(0, len(test_lazy_data), batch_size):
|
| 565 |
+
raw_batch = test_lazy_data[i:i + batch_size]
|
|
|
|
| 566 |
tokenized_batch = [
|
| 567 |
[[self.sp.bos_id()] + self.sp.EncodeAsIds(s) + [self.sp.eos_id()] for s in group]
|
| 568 |
for group in raw_batch
|
|
|
|
| 577 |
output_logits = self.decoder(thought_vectors, group_tensor[:, :-1])
|
| 578 |
val_loss = criterion(output_logits.reshape(-1, self.vocab_size),
|
| 579 |
group_tensor[:, 1:].reshape(-1))
|
| 580 |
+
val_loss += length_penalty * thought_vectors.shape[1]
|
| 581 |
batch_val_loss += val_loss.item()
|
| 582 |
total_val_loss += batch_val_loss
|
| 583 |
+
print(f"VAL batch loss: {batch_val_loss:.4f}")
|
| 584 |
+
print(f" Input: {self.sp.DecodeIds(group_tensor[0].tolist())}")
|
| 585 |
+
print(f"Output: {self.sp.DecodeIds(output_logits.argmax(-1)[0].tolist())}\n\n")
|
| 586 |
avg_val_loss = total_val_loss / num_val_batches if num_val_batches > 0 else float('inf')
|
| 587 |
else:
|
| 588 |
avg_val_loss = avg_train_loss
|
| 589 |
|
| 590 |
+
print(f"Epoch {epoch + 1}/{num_epochs} - Train Loss: {avg_train_loss:.4f} - Val Loss: {avg_val_loss:.4f}\n\n")
|
| 591 |
|
| 592 |
if avg_val_loss < best_val_loss:
|
| 593 |
best_val_loss = avg_val_loss
|
|
|
|
| 628 |
|
| 629 |
return self.encoder(input_tokens, force_single_vector)
|
| 630 |
|
| 631 |
+
def decode(self, thought_vectors: torch.Tensor, max_length: int = 50, beam_width: int = 0,
|
| 632 |
temperature: float = 1.0) -> List[str]:
|
| 633 |
"""Decodes thought vectors into text sequences.
|
| 634 |
|
| 635 |
Args:
|
| 636 |
thought_vectors (torch.Tensor): Thought vectors, shape (batch_size, num_thoughts, d_model).
|
| 637 |
max_length (int, optional): Maximum length of generated sequences. Defaults to 50.
|
| 638 |
+
beam_width (Optional[int], optional): Beam width for beam search; if 0, uses greedy
|
| 639 |
decoding. Defaults to 5.
|
| 640 |
temperature (float, optional): Temperature for softmax sampling in greedy decoding.
|
| 641 |
Defaults to 1.0.
|
|
|
|
| 649 |
thought_vectors = thought_vectors.to(self.device)
|
| 650 |
batch_size = thought_vectors.size(0)
|
| 651 |
|
| 652 |
+
if beam_width > 1:
|
| 653 |
return self._beam_search_decode(thought_vectors, max_length, beam_width)
|
| 654 |
|
| 655 |
target_tokens = torch.full((batch_size, 1), self.sp.bos_id(), dtype=torch.long, device=self.device)
|
| 656 |
for _ in range(max_length - 1):
|
| 657 |
logits = self.decoder(thought_vectors, target_tokens)
|
| 658 |
logits = logits[:, -1, :] / temperature
|
| 659 |
+
|
| 660 |
next_token = logits.softmax(dim=-1).multinomial(1)
|
| 661 |
target_tokens = torch.cat([target_tokens, next_token], dim=1)
|
| 662 |
if (next_token == self.sp.eos_id()).all():
|
|
|
|
| 705 |
results = []
|
| 706 |
for i in range(batch_size):
|
| 707 |
combined = sorted(finished[i] + beams[i], key=lambda x: x[0])
|
| 708 |
+
results.append(self.sp.DecodeIds(combined[0][1].squeeze(0).tolist()) if combined else self.sp.DecodeIds(
|
| 709 |
+
beams[i][0][1].squeeze(0).tolist()))
|
| 710 |
|
| 711 |
return results
|
| 712 |
|
|
|
|
| 760 |
ThoughtVectors: Loaded model instance.
|
| 761 |
"""
|
| 762 |
translator = cls()
|
| 763 |
+
translator.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # load based on available device, not model train device
|
| 764 |
try:
|
| 765 |
with tarfile.open(path, "r") as tar:
|
| 766 |
tar.extractall()
|
| 767 |
+
checkpoint = torch.load("model.pth", map_location=torch.device(translator.device))
|
| 768 |
translator.sp = spm.SentencePieceProcessor()
|
| 769 |
if not translator.sp.Load("spm.model"):
|
| 770 |
raise RuntimeError("Failed to load SentencePiece model.")
|
|
|
|
| 783 |
translator.dropout = checkpoint['dropout']
|
| 784 |
translator.max_len = checkpoint['max_len']
|
| 785 |
translator.termination_threshold = checkpoint['termination_threshold']
|
| 786 |
+
|
| 787 |
|
| 788 |
translator.encoder = ThoughtEncoder(
|
| 789 |
vocab_size=translator.vocab_size, d_model=translator.d_model, max_thoughts=translator.max_thoughts,
|
|
|
|
| 802 |
|
| 803 |
|
| 804 |
if __name__ == "__main__":
|
|
|
|
| 805 |
tv = ThoughtVectors()
|
| 806 |
tv.train(
|
| 807 |
+
group_data="train.csv",
|
| 808 |
+
test_data="val.csv",
|
| 809 |
num_epochs=1000,
|
| 810 |
+
batch_size=128,
|
| 811 |
+
accum_steps=1,
|
| 812 |
+
learning_rate=5e-4,
|
| 813 |
weight_decay=2e-5,
|
| 814 |
+
length_penalty=0.001,
|
| 815 |
single_vector_prob=0.1,
|
| 816 |
save_path="thought_vectors_prototype.tar",
|
| 817 |
spm_model_prefix="spm",
|
|
|
|
| 819 |
d_model=512,
|
| 820 |
encoder_nhead=8,
|
| 821 |
decoder_nhead=8,
|
| 822 |
+
encoder_layers=4,
|
| 823 |
+
decoder_layers=4,
|
| 824 |
+
max_thoughts=16,
|
| 825 |
dropout=0.1,
|
| 826 |
+
max_len=256,
|
| 827 |
termination_threshold=0.8,
|
| 828 |
patience=5
|
| 829 |
)
|
| 830 |
loaded_tv = ThoughtVectors.load("thought_vectors_prototype.tar")
|
| 831 |
thought_vectors = loaded_tv.encode("AI is smart")
|
| 832 |
+
generated_text_greedy = loaded_tv.decode(thought_vectors, temperature=0.7, beam_width=0)
|
| 833 |
+
generated_text_beam = loaded_tv.decode(thought_vectors, beam_width=5)
|
| 834 |
print(f"Greedy decoding: {generated_text_greedy}")
|
| 835 |
print(f"Beam search decoding: {generated_text_beam}")
|
| 836 |
|