noch inator commited on
Updated to handle big data
Browse filesYou can now pass just a csv file for training data. It will only read the first column of the data and ignore all others
- ThoughtVectors.py +239 -171
ThoughtVectors.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
| 1 |
# thought_vectors.py
|
| 2 |
-
import math
|
| 3 |
import random
|
|
|
|
| 4 |
import torch
|
| 5 |
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
|
|
|
|
| 10 |
import os
|
|
|
|
| 11 |
import tempfile
|
| 12 |
import tarfile
|
| 13 |
import shutil
|
|
@@ -42,7 +44,6 @@ class PositionalEncoding(nn.Module):
|
|
| 42 |
# Precompute positional encodings
|
| 43 |
pe = torch.zeros(max_len, d_model)
|
| 44 |
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
|
| 45 |
-
# Adjust div_term for odd d_model
|
| 46 |
div_term = torch.exp(torch.arange(0, d_model - (d_model % 2), 2, dtype=torch.float) *
|
| 47 |
(-math.log(10000.0) / d_model))
|
| 48 |
|
|
@@ -104,7 +105,7 @@ class ThoughtEncoder(nn.Module):
|
|
| 104 |
|
| 105 |
def __init__(self, vocab_size: int, d_model: int = 256, max_thoughts: int = 16,
|
| 106 |
nhead: int = 8, num_layers: int = 2, dropout: float = 0.1, pad_id: int = 0,
|
| 107 |
-
termination_threshold: float = 0.75):
|
| 108 |
super().__init__()
|
| 109 |
assert nhead % 2 == 0, "nhead must be even for thought_attention"
|
| 110 |
self.d_model = d_model
|
|
@@ -112,7 +113,7 @@ class ThoughtEncoder(nn.Module):
|
|
| 112 |
self.pad_id = pad_id
|
| 113 |
self.termination_threshold = termination_threshold
|
| 114 |
self.embedding = nn.Embedding(vocab_size, d_model, padding_idx=pad_id)
|
| 115 |
-
self.positional_encoding = PositionalEncoding(d_model, dropout)
|
| 116 |
encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead, dropout=dropout, batch_first=True)
|
| 117 |
self.text_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
|
| 118 |
self.thought_attention = nn.MultiheadAttention(d_model, num_heads=nhead // 2, dropout=dropout, batch_first=True)
|
|
@@ -181,12 +182,12 @@ class ThoughtDecoder(nn.Module):
|
|
| 181 |
"""
|
| 182 |
|
| 183 |
def __init__(self, vocab_size: int, d_model: int = 256, num_layers: int = 2,
|
| 184 |
-
nhead: int = 8, dropout: float = 0.1):
|
| 185 |
super().__init__()
|
| 186 |
self.vocab_size = vocab_size
|
| 187 |
self.d_model = d_model
|
| 188 |
self.embedding = nn.Embedding(vocab_size, d_model, padding_idx=0)
|
| 189 |
-
self.positional_encoding = PositionalEncoding(d_model, dropout)
|
| 190 |
decoder_layer = nn.TransformerDecoderLayer(d_model=d_model, nhead=nhead, dropout=dropout, batch_first=True)
|
| 191 |
self.transformer_decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_layers)
|
| 192 |
self.fc_out = nn.Linear(d_model, vocab_size)
|
|
@@ -222,30 +223,6 @@ class ThoughtDecoder(nn.Module):
|
|
| 222 |
return self.fc_out(output)
|
| 223 |
|
| 224 |
|
| 225 |
-
class GroupTextDataset(Dataset):
|
| 226 |
-
"""Dataset class for handling groups of text sentences.
|
| 227 |
-
|
| 228 |
-
Args:
|
| 229 |
-
groups (List[List[str]]): List of sentence groups.
|
| 230 |
-
sp (spm.SentencePieceProcessor): SentencePiece processor for tokenization.
|
| 231 |
-
"""
|
| 232 |
-
|
| 233 |
-
def __init__(self, groups: List[List[str]], sp: spm.SentencePieceProcessor):
|
| 234 |
-
self.groups = groups
|
| 235 |
-
self.sp = sp
|
| 236 |
-
|
| 237 |
-
def __len__(self) -> int:
|
| 238 |
-
return len(self.groups)
|
| 239 |
-
|
| 240 |
-
def __getitem__(self, idx: int) -> List[List[int]]:
|
| 241 |
-
group = self.groups[idx]
|
| 242 |
-
tokenized = []
|
| 243 |
-
for sentence in group:
|
| 244 |
-
token_ids = [self.sp.bos_id()] + self.sp.EncodeAsIds(sentence) + [self.sp.eos_id()]
|
| 245 |
-
tokenized.append(token_ids)
|
| 246 |
-
return tokenized
|
| 247 |
-
|
| 248 |
-
|
| 249 |
def group_collate_fn(batch: List[List[List[int]]]) -> List[torch.Tensor]:
|
| 250 |
"""Collates a batch of tokenized groups into padded tensors.
|
| 251 |
|
|
@@ -266,6 +243,111 @@ def group_collate_fn(batch: List[List[List[int]]]) -> List[torch.Tensor]:
|
|
| 266 |
return collated_groups
|
| 267 |
|
| 268 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
class ThoughtVectors:
|
| 270 |
"""Main class for a thought vector model, encoding text into latent vectors and decoding them back.
|
| 271 |
|
|
@@ -291,58 +373,58 @@ class ThoughtVectors:
|
|
| 291 |
self.dropout = None
|
| 292 |
self.max_len = None
|
| 293 |
self.termination_threshold = None
|
| 294 |
-
|
| 295 |
|
| 296 |
def train(self,
|
| 297 |
-
group_data: Union[
|
|
|
|
| 298 |
model: str = None,
|
| 299 |
num_epochs: int = 30,
|
| 300 |
-
batch_size: int =
|
| 301 |
-
|
| 302 |
learning_rate: float = 2e-4,
|
| 303 |
weight_decay: float = 1e-5,
|
| 304 |
length_penalty: float = 0.01,
|
| 305 |
single_vector_prob: float = 0.1,
|
| 306 |
save_path: str = "thought_vectors.tar",
|
| 307 |
spm_model_prefix: str = "spm",
|
| 308 |
-
vocab_size: int =
|
| 309 |
d_model: int = 256,
|
| 310 |
encoder_nhead: int = 8,
|
| 311 |
decoder_nhead: int = 8,
|
| 312 |
encoder_layers: int = 2,
|
| 313 |
decoder_layers: int = 2,
|
| 314 |
-
max_thoughts: int =
|
| 315 |
dropout: float = 0.1,
|
| 316 |
-
max_len: int =
|
| 317 |
termination_threshold: float = 0.75,
|
| 318 |
patience: int = 5) -> None:
|
| 319 |
"""Trains the thought vector model on grouped text data.
|
| 320 |
|
| 321 |
Args:
|
| 322 |
-
group_data (Union[
|
| 323 |
-
|
|
|
|
| 324 |
num_epochs (int, optional): Number of training epochs. Defaults to 30.
|
| 325 |
-
batch_size (int, optional): Batch size. Defaults to
|
| 326 |
-
|
| 327 |
learning_rate (float, optional): Learning rate for Adam optimizer. Defaults to 2e-4.
|
| 328 |
weight_decay (float, optional): Weight decay for regularization. Defaults to 1e-5.
|
| 329 |
length_penalty (float, optional): Penalty per additional thought vector. Defaults to 0.01.
|
| 330 |
single_vector_prob (float, optional): Probability of forcing a single thought vector. Defaults to 0.1.
|
| 331 |
save_path (str, optional): Path to save the model tar file. Defaults to "thought_vectors.tar".
|
| 332 |
spm_model_prefix (str, optional): Prefix for SentencePiece files. Defaults to "spm".
|
| 333 |
-
vocab_size (int, optional): Vocabulary size for tokenization. Defaults to
|
| 334 |
d_model (int, optional): Embedding dimensionality. Defaults to 256.
|
| 335 |
encoder_nhead (int, optional): Number of encoder attention heads. Defaults to 8.
|
| 336 |
decoder_nhead (int, optional): Number of decoder attention heads. Defaults to 8.
|
| 337 |
encoder_layers (int, optional): Number of encoder layers. Defaults to 2.
|
| 338 |
decoder_layers (int, optional): Number of decoder layers. Defaults to 2.
|
| 339 |
-
max_thoughts (int, optional): Maximum number of thought vectors. Defaults to
|
| 340 |
dropout (float, optional): Dropout probability. Defaults to 0.1.
|
| 341 |
-
max_len (int, optional): Maximum sequence length for positional encoding. Defaults to
|
| 342 |
termination_threshold (float, optional): Threshold for stopping thought generation. Defaults to 0.75.
|
| 343 |
patience (int, optional): Epochs to wait for improvement before early stopping. Defaults to 5.
|
| 344 |
"""
|
| 345 |
-
# Set model attributes
|
| 346 |
self.d_model = d_model
|
| 347 |
self.max_thoughts = max_thoughts
|
| 348 |
self.encoder_nhead = encoder_nhead
|
|
@@ -354,152 +436,156 @@ class ThoughtVectors:
|
|
| 354 |
self.termination_threshold = termination_threshold
|
| 355 |
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 356 |
|
| 357 |
-
# Load or create SentencePiece model
|
| 358 |
self.sp = spm.SentencePieceProcessor()
|
|
|
|
|
|
|
|
|
|
| 359 |
if model and os.path.exists(model):
|
| 360 |
-
# Load existing model checkpoint
|
| 361 |
checkpoint = torch.load(model, map_location=self.device)
|
| 362 |
self.sp.Load(checkpoint['spm_model_path'])
|
| 363 |
self.vocab_size = self.sp.GetPieceSize()
|
| 364 |
-
|
| 365 |
-
# Initialize and load encoder/decoder with saved parameters
|
| 366 |
self.encoder = ThoughtEncoder(
|
| 367 |
vocab_size=self.vocab_size, d_model=d_model, max_thoughts=max_thoughts,
|
| 368 |
nhead=encoder_nhead, num_layers=encoder_layers, dropout=dropout,
|
| 369 |
-
termination_threshold=termination_threshold
|
| 370 |
).to(self.device)
|
| 371 |
self.decoder = ThoughtDecoder(
|
| 372 |
vocab_size=self.vocab_size, d_model=d_model, num_layers=decoder_layers,
|
| 373 |
-
nhead=decoder_nhead, dropout=dropout
|
| 374 |
).to(self.device)
|
| 375 |
-
|
| 376 |
self.encoder.load_state_dict(checkpoint['encoder_state_dict'])
|
| 377 |
self.decoder.load_state_dict(checkpoint['decoder_state_dict'])
|
| 378 |
print(f"Loaded pre-existing model from {model}")
|
| 379 |
else:
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
with tempfile.NamedTemporaryFile(mode="w", encoding="utf8", delete=False) as temp_file:
|
| 386 |
-
for line in all_sentences:
|
| 387 |
-
temp_file.write(line.strip() + "\n")
|
| 388 |
-
temp_dataset_path = temp_file.name
|
| 389 |
-
else:
|
| 390 |
-
# Handle generator input
|
| 391 |
-
with tempfile.NamedTemporaryFile(mode="w", encoding="utf8", delete=False) as temp_file:
|
| 392 |
-
for group in group_data:
|
| 393 |
-
for sentence in group:
|
| 394 |
-
temp_file.write(sentence.strip() + "\n")
|
| 395 |
-
temp_dataset_path = temp_file.name
|
| 396 |
|
| 397 |
temp_dir = tempfile.mkdtemp()
|
| 398 |
temp_spm_prefix = os.path.join(temp_dir, "spm")
|
| 399 |
-
|
| 400 |
try:
|
| 401 |
spm.SentencePieceTrainer.Train(
|
| 402 |
input=temp_dataset_path,
|
| 403 |
model_prefix=temp_spm_prefix,
|
| 404 |
vocab_size=vocab_size,
|
| 405 |
model_type="bpe",
|
| 406 |
-
pad_id=0,
|
| 407 |
-
|
| 408 |
-
bos_id=2,
|
| 409 |
-
eos_id=3,
|
| 410 |
-
max_sentence_length=8192, # Added to handle long sequences
|
| 411 |
input_sentence_size=8_388_608,
|
| 412 |
-
train_extremely_large_corpus=True
|
| 413 |
)
|
| 414 |
shutil.copy(f"{temp_spm_prefix}.model", f"{spm_model_prefix}.model")
|
| 415 |
shutil.copy(f"{temp_spm_prefix}.vocab", f"{spm_model_prefix}.vocab")
|
| 416 |
-
|
| 417 |
if not self.sp.Load(f"{spm_model_prefix}.model"):
|
| 418 |
raise RuntimeError("Failed to load SentencePiece model.")
|
| 419 |
finally:
|
| 420 |
-
|
| 421 |
-
os.remove(temp_dataset_path)
|
| 422 |
shutil.rmtree(temp_dir, ignore_errors=True)
|
| 423 |
|
| 424 |
self.vocab_size = self.sp.GetPieceSize()
|
| 425 |
self.encoder = ThoughtEncoder(
|
| 426 |
vocab_size=self.vocab_size, d_model=d_model, max_thoughts=max_thoughts,
|
| 427 |
nhead=encoder_nhead, num_layers=encoder_layers, dropout=dropout,
|
| 428 |
-
termination_threshold=termination_threshold
|
| 429 |
).to(self.device)
|
| 430 |
self.decoder = ThoughtDecoder(
|
| 431 |
vocab_size=self.vocab_size, d_model=d_model, num_layers=decoder_layers,
|
| 432 |
-
nhead=decoder_nhead, dropout=dropout
|
| 433 |
).to(self.device)
|
| 434 |
|
| 435 |
-
# Rest of the training setup
|
| 436 |
-
dataset = GroupTextDataset(group_data, self.sp)
|
| 437 |
-
train_size = int(val_split * len(dataset))
|
| 438 |
-
val_size = len(dataset) - train_size
|
| 439 |
-
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])
|
| 440 |
-
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=group_collate_fn)
|
| 441 |
-
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, collate_fn=group_collate_fn)
|
| 442 |
-
|
| 443 |
optimizer = optim.Adam(list(self.encoder.parameters()) + list(self.decoder.parameters()),
|
| 444 |
lr=learning_rate, weight_decay=weight_decay)
|
| 445 |
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_epochs)
|
| 446 |
criterion = nn.CrossEntropyLoss(ignore_index=self.sp.pad_id())
|
|
|
|
| 447 |
|
| 448 |
best_val_loss = float('inf')
|
| 449 |
patience_counter = 0
|
| 450 |
|
| 451 |
-
|
| 452 |
for epoch in range(num_epochs):
|
| 453 |
self.encoder.train()
|
| 454 |
self.decoder.train()
|
| 455 |
total_train_loss = 0.0
|
| 456 |
-
|
| 457 |
-
optimizer.zero_grad()
|
| 458 |
-
batch_loss = 0.0
|
| 459 |
-
for group_tensor in group_batch:
|
| 460 |
-
if group_tensor.numel() == 0:
|
| 461 |
-
continue
|
| 462 |
-
group_tensor = group_tensor.to(self.device)
|
| 463 |
-
force_single_vector = random.random() < single_vector_prob
|
| 464 |
-
thought_vectors = self.encoder(group_tensor, force_single_vector=force_single_vector)
|
| 465 |
-
|
| 466 |
-
decoder_input = group_tensor[:, :-1]
|
| 467 |
-
target_tokens = group_tensor[:, 1:]
|
| 468 |
-
output_logits = self.decoder(thought_vectors, decoder_input)
|
| 469 |
-
|
| 470 |
-
loss = criterion(output_logits.reshape(-1, self.decoder.vocab_size), target_tokens.reshape(-1))
|
| 471 |
-
if not force_single_vector:
|
| 472 |
-
loss += length_penalty * thought_vectors.shape[1]
|
| 473 |
-
batch_loss += loss
|
| 474 |
-
|
| 475 |
-
if batch_loss > 0:
|
| 476 |
-
batch_loss.backward()
|
| 477 |
-
optimizer.step()
|
| 478 |
-
total_train_loss += batch_loss.item()
|
| 479 |
|
| 480 |
-
|
|
|
|
|
|
|
|
|
|
| 481 |
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
for group_tensor in group_batch:
|
| 489 |
-
if group_tensor.numel()
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
group_tensor[:, 1:].reshape(-1))
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
|
| 500 |
-
|
| 501 |
-
print(f"Epoch {epoch + 1}/{num_epochs} - Train Loss: {avg_train_loss:.4f} - "
|
| 502 |
-
f"Val Loss: {avg_val_loss:.4f}")
|
| 503 |
|
| 504 |
if avg_val_loss < best_val_loss:
|
| 505 |
best_val_loss = avg_val_loss
|
|
@@ -647,7 +733,8 @@ class ThoughtVectors:
|
|
| 647 |
'decoder_layers': self.decoder_layers,
|
| 648 |
'dropout': self.dropout,
|
| 649 |
'max_len': self.max_len,
|
| 650 |
-
'termination_threshold': self.termination_threshold
|
|
|
|
| 651 |
}, "model.pth")
|
| 652 |
tar.add("model.pth")
|
| 653 |
tar.add(f"{spm_model_prefix}.model")
|
|
@@ -695,11 +782,11 @@ class ThoughtVectors:
|
|
| 695 |
translator.encoder = ThoughtEncoder(
|
| 696 |
vocab_size=translator.vocab_size, d_model=translator.d_model, max_thoughts=translator.max_thoughts,
|
| 697 |
nhead=translator.encoder_nhead, num_layers=translator.encoder_layers, dropout=translator.dropout,
|
| 698 |
-
termination_threshold=translator.termination_threshold
|
| 699 |
).to(translator.device)
|
| 700 |
translator.decoder = ThoughtDecoder(
|
| 701 |
vocab_size=translator.vocab_size, d_model=translator.d_model, num_layers=translator.decoder_layers,
|
| 702 |
-
nhead=translator.decoder_nhead, dropout=translator.dropout
|
| 703 |
).to(translator.device)
|
| 704 |
|
| 705 |
translator.encoder.load_state_dict(checkpoint['encoder_state_dict'])
|
|
@@ -708,55 +795,36 @@ class ThoughtVectors:
|
|
| 708 |
return translator
|
| 709 |
|
| 710 |
|
| 711 |
-
# Example usage
|
| 712 |
if __name__ == "__main__":
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
csv.field_size_limit(8 ** 8) # You can adjust this to a higher limit (e.g., 1MB)
|
| 716 |
-
|
| 717 |
-
# Generator function to read data from a CSV and format it
|
| 718 |
-
def data_generator(csv_file_path):
|
| 719 |
-
with open(csv_file_path, mode='r', newline='', encoding='utf-8') as file:
|
| 720 |
-
reader = csv.reader(file)
|
| 721 |
-
for row in reader:
|
| 722 |
-
if row:
|
| 723 |
-
yield [row[0]] # assuming first collumn is sentences
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
data = data_generator("path/to/csv")
|
| 727 |
-
|
| 728 |
-
# ThoughtVectors training and usage process
|
| 729 |
tv = ThoughtVectors()
|
| 730 |
tv.train(
|
| 731 |
-
group_data=data,
|
| 732 |
num_epochs=1000,
|
| 733 |
-
batch_size=
|
| 734 |
-
|
| 735 |
-
learning_rate=
|
| 736 |
-
weight_decay=
|
| 737 |
length_penalty=0.01,
|
| 738 |
-
single_vector_prob=0.
|
| 739 |
save_path="thought_vectors_prototype.tar",
|
| 740 |
spm_model_prefix="spm",
|
| 741 |
vocab_size=8192,
|
| 742 |
-
d_model=
|
| 743 |
encoder_nhead=8,
|
| 744 |
decoder_nhead=8,
|
| 745 |
-
encoder_layers=
|
| 746 |
-
decoder_layers=
|
| 747 |
max_thoughts=32,
|
| 748 |
dropout=0.1,
|
| 749 |
-
max_len=
|
| 750 |
-
termination_threshold=0.
|
| 751 |
-
patience=
|
| 752 |
)
|
| 753 |
-
|
| 754 |
-
# Load and test
|
| 755 |
loaded_tv = ThoughtVectors.load("thought_vectors_prototype.tar")
|
| 756 |
-
|
| 757 |
-
generated_text_greedy =
|
| 758 |
-
generated_text_beam =
|
| 759 |
-
|
| 760 |
print(f"Greedy decoding: {generated_text_greedy}")
|
| 761 |
print(f"Beam search decoding: {generated_text_beam}")
|
| 762 |
|
|
|
|
| 1 |
# thought_vectors.py
|
|
|
|
| 2 |
import random
|
| 3 |
+
import math
|
| 4 |
import torch
|
| 5 |
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.cuda.amp import autocast, GradScaler
|
| 10 |
+
from typing import List, Union, Generator, Sequence, Optional
|
| 11 |
import os
|
| 12 |
+
import csv
|
| 13 |
import tempfile
|
| 14 |
import tarfile
|
| 15 |
import shutil
|
|
|
|
| 44 |
# Precompute positional encodings
|
| 45 |
pe = torch.zeros(max_len, d_model)
|
| 46 |
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
|
|
|
|
| 47 |
div_term = torch.exp(torch.arange(0, d_model - (d_model % 2), 2, dtype=torch.float) *
|
| 48 |
(-math.log(10000.0) / d_model))
|
| 49 |
|
|
|
|
| 105 |
|
| 106 |
def __init__(self, vocab_size: int, d_model: int = 256, max_thoughts: int = 16,
|
| 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 |
assert nhead % 2 == 0, "nhead must be even for thought_attention"
|
| 111 |
self.d_model = d_model
|
|
|
|
| 113 |
self.pad_id = pad_id
|
| 114 |
self.termination_threshold = termination_threshold
|
| 115 |
self.embedding = nn.Embedding(vocab_size, d_model, padding_idx=pad_id)
|
| 116 |
+
self.positional_encoding = PositionalEncoding(d_model, dropout, max_len=max_len)
|
| 117 |
encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead, dropout=dropout, batch_first=True)
|
| 118 |
self.text_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
|
| 119 |
self.thought_attention = nn.MultiheadAttention(d_model, num_heads=nhead // 2, dropout=dropout, batch_first=True)
|
|
|
|
| 182 |
"""
|
| 183 |
|
| 184 |
def __init__(self, vocab_size: int, d_model: int = 256, num_layers: int = 2,
|
| 185 |
+
nhead: int = 8, dropout: float = 0.1, max_len: int = 5000):
|
| 186 |
super().__init__()
|
| 187 |
self.vocab_size = vocab_size
|
| 188 |
self.d_model = d_model
|
| 189 |
self.embedding = nn.Embedding(vocab_size, d_model, padding_idx=0)
|
| 190 |
+
self.positional_encoding = PositionalEncoding(d_model, dropout, max_len)
|
| 191 |
decoder_layer = nn.TransformerDecoderLayer(d_model=d_model, nhead=nhead, dropout=dropout, batch_first=True)
|
| 192 |
self.transformer_decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_layers)
|
| 193 |
self.fc_out = nn.Linear(d_model, vocab_size)
|
|
|
|
| 223 |
return self.fc_out(output)
|
| 224 |
|
| 225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
def group_collate_fn(batch: List[List[List[int]]]) -> List[torch.Tensor]:
|
| 227 |
"""Collates a batch of tokenized groups into padded tensors.
|
| 228 |
|
|
|
|
| 243 |
return collated_groups
|
| 244 |
|
| 245 |
|
| 246 |
+
class LazyList(Sequence):
|
| 247 |
+
def __init__(self, data: Union[str, List[List[str]]], sample_prob: float = 0.75):
|
| 248 |
+
"""Initialize LazyList with data source and sampling probability.
|
| 249 |
+
|
| 250 |
+
Args:
|
| 251 |
+
data (Union[str, List[List[str]]]): A filepath to a CSV or a list of sentence groups.
|
| 252 |
+
sample_prob (float, optional): Probability of returning a row in __getitem__ (0.0 to 1.0).
|
| 253 |
+
Defaults to 0.75 (75% chance to return, 25% to skip).
|
| 254 |
+
"""
|
| 255 |
+
if isinstance(data, str):
|
| 256 |
+
self.filepath = data
|
| 257 |
+
self.data_list = None
|
| 258 |
+
elif isinstance(data, list):
|
| 259 |
+
self.filepath = None
|
| 260 |
+
self.data_list = data
|
| 261 |
+
else:
|
| 262 |
+
raise ValueError("Data must be a filepath (str) or a list of lists of strings")
|
| 263 |
+
self.sample_prob = max(0.0, min(1.0, sample_prob)) # Clamp between 0 and 1
|
| 264 |
+
self._length = None
|
| 265 |
+
self._file_handle = None # Persistent file handle for sequential reading
|
| 266 |
+
self._reader = None # Persistent CSV reader
|
| 267 |
+
|
| 268 |
+
def _open_file(self):
|
| 269 |
+
"""Open the file and initialize the reader if not already open."""
|
| 270 |
+
if self.filepath and os.path.exists(self.filepath) and self._file_handle is None:
|
| 271 |
+
self._file_handle = open(self.filepath, 'r', encoding='utf-8', newline='')
|
| 272 |
+
self._reader = csv.reader(self._file_handle)
|
| 273 |
+
|
| 274 |
+
def __iter__(self) -> Generator[List[str], None, None]:
|
| 275 |
+
"""Iterate through all rows sequentially without sampling."""
|
| 276 |
+
if self.data_list is not None:
|
| 277 |
+
yield from self.data_list
|
| 278 |
+
elif self.filepath and os.path.exists(self.filepath):
|
| 279 |
+
with open(self.filepath, 'r', encoding='utf-8', newline='') as f:
|
| 280 |
+
reader = csv.reader(f)
|
| 281 |
+
for row in reader:
|
| 282 |
+
if row:
|
| 283 |
+
yield row
|
| 284 |
+
else:
|
| 285 |
+
dummy_data = [
|
| 286 |
+
["Hello world", "AI is cool"],
|
| 287 |
+
["This is a test", "Another sentence"],
|
| 288 |
+
["Python is fun", "Coding rocks"],
|
| 289 |
+
["Short sentence", "Quick test"],
|
| 290 |
+
]
|
| 291 |
+
yield from dummy_data
|
| 292 |
+
|
| 293 |
+
def __len__(self) -> int:
|
| 294 |
+
"""Compute and cache the total number of rows."""
|
| 295 |
+
if self._length is None:
|
| 296 |
+
if self.data_list is not None:
|
| 297 |
+
self._length = len(self.data_list)
|
| 298 |
+
elif self.filepath and os.path.exists(self.filepath):
|
| 299 |
+
with open(self.filepath, 'r', encoding='utf-8', newline='') as file:
|
| 300 |
+
reader = csv.reader(file)
|
| 301 |
+
self._length = sum(1 for row in reader if row)
|
| 302 |
+
else:
|
| 303 |
+
self._length = 4
|
| 304 |
+
return self._length
|
| 305 |
+
|
| 306 |
+
def __getitem__(self, index: int) -> List[str]:
|
| 307 |
+
"""Return a single row with semi-random skipping; index is ignored.
|
| 308 |
+
|
| 309 |
+
Args:
|
| 310 |
+
index (int): Ignored parameter (kept for Sequence compatibility).
|
| 311 |
+
|
| 312 |
+
Returns:
|
| 313 |
+
List[str]: A row from the data, with ~25% chance of skipping each row.
|
| 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: # 75% chance to return
|
| 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) # Return first row if available
|
| 331 |
+
if row:
|
| 332 |
+
return row
|
| 333 |
+
|
| 334 |
+
# Dummy data fallback
|
| 335 |
+
dummy_data = [
|
| 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."""
|
| 345 |
+
if self._file_handle is not None:
|
| 346 |
+
self._file_handle.close()
|
| 347 |
+
self._file_handle = None
|
| 348 |
+
self._reader = None
|
| 349 |
+
|
| 350 |
+
|
| 351 |
class ThoughtVectors:
|
| 352 |
"""Main class for a thought vector model, encoding text into latent vectors and decoding them back.
|
| 353 |
|
|
|
|
| 373 |
self.dropout = None
|
| 374 |
self.max_len = None
|
| 375 |
self.termination_threshold = None
|
|
|
|
| 376 |
|
| 377 |
def train(self,
|
| 378 |
+
group_data: Union[str, List[List[str]]],
|
| 379 |
+
test_data: Union[str, List[List[str]]] = None,
|
| 380 |
model: str = None,
|
| 381 |
num_epochs: int = 30,
|
| 382 |
+
batch_size: int = 8,
|
| 383 |
+
accum_steps: int = 4,
|
| 384 |
learning_rate: float = 2e-4,
|
| 385 |
weight_decay: float = 1e-5,
|
| 386 |
length_penalty: float = 0.01,
|
| 387 |
single_vector_prob: float = 0.1,
|
| 388 |
save_path: str = "thought_vectors.tar",
|
| 389 |
spm_model_prefix: str = "spm",
|
| 390 |
+
vocab_size: int = 4096,
|
| 391 |
d_model: int = 256,
|
| 392 |
encoder_nhead: int = 8,
|
| 393 |
decoder_nhead: int = 8,
|
| 394 |
encoder_layers: int = 2,
|
| 395 |
decoder_layers: int = 2,
|
| 396 |
+
max_thoughts: int = 8,
|
| 397 |
dropout: float = 0.1,
|
| 398 |
+
max_len: int = 1024,
|
| 399 |
termination_threshold: float = 0.75,
|
| 400 |
patience: int = 5) -> None:
|
| 401 |
"""Trains the thought vector model on grouped text data.
|
| 402 |
|
| 403 |
Args:
|
| 404 |
+
group_data (Union[str, List[List[str]]]): Filepath to a CSV or list of sentence groups for training.
|
| 405 |
+
test_data (Union[str, List[List[str]]], optional): Filepath to a CSV or list of sentence groups for validation. If None, no validation is performed. Defaults to None.
|
| 406 |
+
model (str, optional): Path to pre-existing model to load. If None, creates a new model. Defaults to None.
|
| 407 |
num_epochs (int, optional): Number of training epochs. Defaults to 30.
|
| 408 |
+
batch_size (int, optional): Batch size. Defaults to 8.
|
| 409 |
+
accum_steps (int, optional): Number of gradient accumulation steps. Defaults to 4.
|
| 410 |
learning_rate (float, optional): Learning rate for Adam optimizer. Defaults to 2e-4.
|
| 411 |
weight_decay (float, optional): Weight decay for regularization. Defaults to 1e-5.
|
| 412 |
length_penalty (float, optional): Penalty per additional thought vector. Defaults to 0.01.
|
| 413 |
single_vector_prob (float, optional): Probability of forcing a single thought vector. Defaults to 0.1.
|
| 414 |
save_path (str, optional): Path to save the model tar file. Defaults to "thought_vectors.tar".
|
| 415 |
spm_model_prefix (str, optional): Prefix for SentencePiece files. Defaults to "spm".
|
| 416 |
+
vocab_size (int, optional): Vocabulary size for tokenization. Defaults to 4096.
|
| 417 |
d_model (int, optional): Embedding dimensionality. Defaults to 256.
|
| 418 |
encoder_nhead (int, optional): Number of encoder attention heads. Defaults to 8.
|
| 419 |
decoder_nhead (int, optional): Number of decoder attention heads. Defaults to 8.
|
| 420 |
encoder_layers (int, optional): Number of encoder layers. Defaults to 2.
|
| 421 |
decoder_layers (int, optional): Number of decoder layers. Defaults to 2.
|
| 422 |
+
max_thoughts (int, optional): Maximum number of thought vectors. Defaults to 8.
|
| 423 |
dropout (float, optional): Dropout probability. Defaults to 0.1.
|
| 424 |
+
max_len (int, optional): Maximum sequence length for positional encoding. Defaults to 1024.
|
| 425 |
termination_threshold (float, optional): Threshold for stopping thought generation. Defaults to 0.75.
|
| 426 |
patience (int, optional): Epochs to wait for improvement before early stopping. Defaults to 5.
|
| 427 |
"""
|
|
|
|
| 428 |
self.d_model = d_model
|
| 429 |
self.max_thoughts = max_thoughts
|
| 430 |
self.encoder_nhead = encoder_nhead
|
|
|
|
| 436 |
self.termination_threshold = termination_threshold
|
| 437 |
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 438 |
|
|
|
|
| 439 |
self.sp = spm.SentencePieceProcessor()
|
| 440 |
+
train_lazy_data = LazyList(group_data)
|
| 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()
|
|
|
|
|
|
|
| 447 |
self.encoder = ThoughtEncoder(
|
| 448 |
vocab_size=self.vocab_size, d_model=d_model, max_thoughts=max_thoughts,
|
| 449 |
nhead=encoder_nhead, num_layers=encoder_layers, dropout=dropout,
|
| 450 |
+
termination_threshold=termination_threshold, max_len=self.max_len
|
| 451 |
).to(self.device)
|
| 452 |
self.decoder = ThoughtDecoder(
|
| 453 |
vocab_size=self.vocab_size, d_model=d_model, num_layers=decoder_layers,
|
| 454 |
+
nhead=decoder_nhead, dropout=dropout, max_len=self.max_len
|
| 455 |
).to(self.device)
|
|
|
|
| 456 |
self.encoder.load_state_dict(checkpoint['encoder_state_dict'])
|
| 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:
|
| 463 |
+
temp_file.write(sentence.strip() + "\n")
|
| 464 |
+
temp_dataset_path = temp_file.name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
|
| 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,
|
| 472 |
vocab_size=vocab_size,
|
| 473 |
model_type="bpe",
|
| 474 |
+
pad_id=0, unk_id=1, bos_id=2, eos_id=3,
|
| 475 |
+
max_sentence_length=self.max_len,
|
|
|
|
|
|
|
|
|
|
| 476 |
input_sentence_size=8_388_608,
|
| 477 |
+
train_extremely_large_corpus=True
|
| 478 |
)
|
| 479 |
shutil.copy(f"{temp_spm_prefix}.model", f"{spm_model_prefix}.model")
|
| 480 |
shutil.copy(f"{temp_spm_prefix}.vocab", f"{spm_model_prefix}.vocab")
|
|
|
|
| 481 |
if not self.sp.Load(f"{spm_model_prefix}.model"):
|
| 482 |
raise RuntimeError("Failed to load SentencePiece model.")
|
| 483 |
finally:
|
| 484 |
+
os.remove(temp_dataset_path)
|
|
|
|
| 485 |
shutil.rmtree(temp_dir, ignore_errors=True)
|
| 486 |
|
| 487 |
self.vocab_size = self.sp.GetPieceSize()
|
| 488 |
self.encoder = ThoughtEncoder(
|
| 489 |
vocab_size=self.vocab_size, d_model=d_model, max_thoughts=max_thoughts,
|
| 490 |
nhead=encoder_nhead, num_layers=encoder_layers, dropout=dropout,
|
| 491 |
+
termination_threshold=termination_threshold, max_len=self.max_len
|
| 492 |
).to(self.device)
|
| 493 |
self.decoder = ThoughtDecoder(
|
| 494 |
vocab_size=self.vocab_size, d_model=d_model, num_layers=decoder_layers,
|
| 495 |
+
nhead=decoder_nhead, dropout=dropout, max_len=self.max_len
|
| 496 |
).to(self.device)
|
| 497 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 498 |
optimizer = optim.Adam(list(self.encoder.parameters()) + list(self.decoder.parameters()),
|
| 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 |
+
optimizer.zero_grad()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 513 |
|
| 514 |
+
# Manual batching from train_lazy_data with tokenization
|
| 515 |
+
train_data_list = list(train_lazy_data) # List[str]
|
| 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) # Process into padded tensors
|
| 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 |
+
total_train_loss += batch_loss.item() * accum_steps
|
| 550 |
+
print(
|
| 551 |
+
f"Batch {i // batch_size + 1}/{num_train_batches} - Train Loss: {batch_loss:.4f}")
|
| 552 |
+
|
| 553 |
+
avg_train_loss = total_train_loss / num_train_batches if num_train_batches > 0 else float('inf')
|
| 554 |
+
|
| 555 |
+
# Validation with test_lazy_data
|
| 556 |
+
if test_lazy_data is not None:
|
| 557 |
+
self.encoder.eval()
|
| 558 |
+
self.decoder.eval()
|
| 559 |
+
total_val_loss = 0.0
|
| 560 |
+
test_data_list = list(test_lazy_data)
|
| 561 |
+
num_val_batches = (len(test_data_list) + batch_size - 1) // batch_size
|
| 562 |
+
|
| 563 |
+
with torch.no_grad():
|
| 564 |
+
for i in range(0, len(test_data_list), batch_size):
|
| 565 |
+
raw_batch = test_data_list[i:i + batch_size]
|
| 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
|
| 570 |
+
]
|
| 571 |
+
group_batch = group_collate_fn(tokenized_batch)
|
| 572 |
+
|
| 573 |
+
batch_val_loss = 0.0
|
| 574 |
+
for group_tensor in group_batch:
|
| 575 |
+
if group_tensor.shape[1] <= self.max_len and group_tensor.numel() > 0:
|
| 576 |
+
group_tensor = group_tensor.to(self.device)
|
| 577 |
+
thought_vectors = self.encoder(group_tensor)
|
| 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
|
|
|
|
| 733 |
'decoder_layers': self.decoder_layers,
|
| 734 |
'dropout': self.dropout,
|
| 735 |
'max_len': self.max_len,
|
| 736 |
+
'termination_threshold': self.termination_threshold,
|
| 737 |
+
'spm_model_path': f"{spm_model_prefix}.model" # Added to match loading logic
|
| 738 |
}, "model.pth")
|
| 739 |
tar.add("model.pth")
|
| 740 |
tar.add(f"{spm_model_prefix}.model")
|
|
|
|
| 782 |
translator.encoder = ThoughtEncoder(
|
| 783 |
vocab_size=translator.vocab_size, d_model=translator.d_model, max_thoughts=translator.max_thoughts,
|
| 784 |
nhead=translator.encoder_nhead, num_layers=translator.encoder_layers, dropout=translator.dropout,
|
| 785 |
+
termination_threshold=translator.termination_threshold, max_len=translator.max_len
|
| 786 |
).to(translator.device)
|
| 787 |
translator.decoder = ThoughtDecoder(
|
| 788 |
vocab_size=translator.vocab_size, d_model=translator.d_model, num_layers=translator.decoder_layers,
|
| 789 |
+
nhead=translator.decoder_nhead, dropout=translator.dropout, max_len=translator.max_len
|
| 790 |
).to(translator.device)
|
| 791 |
|
| 792 |
translator.encoder.load_state_dict(checkpoint['encoder_state_dict'])
|
|
|
|
| 795 |
return translator
|
| 796 |
|
| 797 |
|
|
|
|
| 798 |
if __name__ == "__main__":
|
| 799 |
+
csv.field_size_limit(8 ** 8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 800 |
tv = ThoughtVectors()
|
| 801 |
tv.train(
|
| 802 |
+
group_data="data.csv",
|
| 803 |
num_epochs=1000,
|
| 804 |
+
batch_size=64,
|
| 805 |
+
accum_steps=4,
|
| 806 |
+
learning_rate=1e-4,
|
| 807 |
+
weight_decay=2e-5,
|
| 808 |
length_penalty=0.01,
|
| 809 |
+
single_vector_prob=0.1,
|
| 810 |
save_path="thought_vectors_prototype.tar",
|
| 811 |
spm_model_prefix="spm",
|
| 812 |
vocab_size=8192,
|
| 813 |
+
d_model=512,
|
| 814 |
encoder_nhead=8,
|
| 815 |
decoder_nhead=8,
|
| 816 |
+
encoder_layers=2,
|
| 817 |
+
decoder_layers=2,
|
| 818 |
max_thoughts=32,
|
| 819 |
dropout=0.1,
|
| 820 |
+
max_len=1024,
|
| 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=3)
|
|
|
|
| 828 |
print(f"Greedy decoding: {generated_text_greedy}")
|
| 829 |
print(f"Beam search decoding: {generated_text_beam}")
|
| 830 |
|