noch inator commited on
Commit
39bc049
·
verified ·
1 Parent(s): 165397d

Updated to handle big data

Browse files

You can now pass just a csv file for training data. It will only read the first column of the data and ignore all others

Files changed (1) hide show
  1. 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, random_split
9
- from typing import List, Union, Optional, Iterator
 
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[List[List[str]], Iterator[List[str]]],
 
298
  model: str = None,
299
  num_epochs: int = 30,
300
- batch_size: int = 1,
301
- val_split: float = 0.8,
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 = 2000,
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 = 16,
315
  dropout: float = 0.1,
316
- max_len: int = 5000,
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[List[List[str]], Iterator[List[str]]]): Either a list of sentence groups or a generator yielding sentence groups for training.
323
- model (str): Path to pre-existing model to load. If None, creates a new model. Defaults to None
 
324
  num_epochs (int, optional): Number of training epochs. Defaults to 30.
325
- batch_size (int, optional): Batch size. Defaults to 1.
326
- val_split (float, optional): Fraction of data for training. Defaults to 0.8.
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 2000.
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 16.
340
  dropout (float, optional): Dropout probability. Defaults to 0.1.
341
- max_len (int, optional): Maximum sequence length for positional encoding. Defaults to 5000.
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
- # Create new SentencePiece model
381
- temp_dataset_path = None
382
- if isinstance(group_data, (list, tuple)):
383
- # Handle list input as before
384
- all_sentences = [sentence for group in group_data for sentence in group]
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
- unk_id=1,
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 # Optimize for large datasets
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
- if temp_dataset_path:
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
- # Training loop
452
  for epoch in range(num_epochs):
453
  self.encoder.train()
454
  self.decoder.train()
455
  total_train_loss = 0.0
456
- for group_batch in train_loader:
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
- avg_train_loss = total_train_loss / len(train_loader)
 
 
 
481
 
482
- self.encoder.eval()
483
- self.decoder.eval()
484
- total_val_loss = 0.0
485
- with torch.no_grad():
486
- for group_batch in val_loader:
487
- batch_val_loss = 0.0
 
 
 
 
 
488
  for group_tensor in group_batch:
489
- if group_tensor.numel() == 0:
490
- continue
491
- group_tensor = group_tensor.to(self.device)
492
- thought_vectors = self.encoder(group_tensor)
493
- output_logits = self.decoder(thought_vectors, group_tensor[:, :-1])
494
- val_loss = criterion(output_logits.reshape(-1, self.decoder.vocab_size),
495
  group_tensor[:, 1:].reshape(-1))
496
- batch_val_loss += val_loss.item()
497
-
498
- total_val_loss += batch_val_loss
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
 
500
- avg_val_loss = total_val_loss / len(val_loader)
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
- import csv
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=128,
734
- val_split=0.8,
735
- learning_rate=2e-4,
736
- weight_decay=5e-5,
737
  length_penalty=0.01,
738
- single_vector_prob=0.2,
739
  save_path="thought_vectors_prototype.tar",
740
  spm_model_prefix="spm",
741
  vocab_size=8192,
742
- d_model=1024,
743
  encoder_nhead=8,
744
  decoder_nhead=8,
745
- encoder_layers=4,
746
- decoder_layers=4,
747
  max_thoughts=32,
748
  dropout=0.1,
749
- max_len=256,
750
- termination_threshold=0.9,
751
- patience=10
752
  )
753
-
754
- # Load and test
755
  loaded_tv = ThoughtVectors.load("thought_vectors_prototype.tar")
756
- loaded_thought_vectors = loaded_tv.encode("AI is smart")
757
- generated_text_greedy = tv.decode(loaded_thought_vectors, temperature=0.7)
758
- generated_text_beam = tv.decode(loaded_thought_vectors, beam_width=3)
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