noch inator commited on
Upload ThoughtVectors.py
Browse files- ThoughtVectors.py +762 -0
ThoughtVectors.py
ADDED
|
@@ -0,0 +1,762 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class PositionalEncoding(nn.Module):
|
| 17 |
+
"""Adds positional information to token embeddings using sine and cosine functions.
|
| 18 |
+
|
| 19 |
+
This module can either precompute positional encodings up to a specified `max_len` or compute
|
| 20 |
+
them dynamically based on the input sequence length. If `max_len` is 0, encodings are computed
|
| 21 |
+
on-the-fly in the forward pass; otherwise, they are precomputed during initialization.
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
d_model (int): Dimensionality of the model embeddings.
|
| 25 |
+
dropout (float, optional): Dropout probability applied after adding encodings. Defaults to 0.1.
|
| 26 |
+
max_len (int, optional): Maximum sequence length for precomputed encodings; if 0, computes
|
| 27 |
+
dynamically. Defaults to 5000.
|
| 28 |
+
|
| 29 |
+
Attributes:
|
| 30 |
+
dropout (nn.Dropout): Dropout layer for regularization.
|
| 31 |
+
pe (torch.Tensor, optional): Precomputed positional encodings, shape (1, max_len, d_model),
|
| 32 |
+
present only if max_len > 0.
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
def __init__(self, d_model: int, dropout: float = 0.1, max_len: int = 5000):
|
| 36 |
+
super().__init__()
|
| 37 |
+
self.d_model = d_model
|
| 38 |
+
self.dropout = nn.Dropout(dropout)
|
| 39 |
+
self.max_len = max_len
|
| 40 |
+
|
| 41 |
+
if max_len > 0:
|
| 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 |
+
|
| 49 |
+
pe[:, 0::2] = torch.sin(position * div_term)
|
| 50 |
+
pe[:, 1::2] = torch.cos(position * div_term[:d_model // 2 + 1] if d_model % 2 else div_term)
|
| 51 |
+
|
| 52 |
+
pe = pe.unsqueeze(0)
|
| 53 |
+
self.register_buffer('pe', pe)
|
| 54 |
+
else:
|
| 55 |
+
self.pe = None
|
| 56 |
+
|
| 57 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 58 |
+
"""Applies positional encodings to the input tensor.
|
| 59 |
+
|
| 60 |
+
Args:
|
| 61 |
+
x (torch.Tensor): Input embeddings, shape (batch_size, seq_len, d_model).
|
| 62 |
+
|
| 63 |
+
Returns:
|
| 64 |
+
torch.Tensor: Embeddings with positional encodings added and dropout applied.
|
| 65 |
+
|
| 66 |
+
Raises:
|
| 67 |
+
ValueError: If max_len > 0 and seq_len exceeds max_len.
|
| 68 |
+
"""
|
| 69 |
+
seq_len = x.size(1)
|
| 70 |
+
|
| 71 |
+
if self.max_len > 0:
|
| 72 |
+
if seq_len > self.max_len:
|
| 73 |
+
raise ValueError(f"Sequence length {seq_len} exceeds max_len {self.max_len}")
|
| 74 |
+
pe = self.pe[:, :seq_len]
|
| 75 |
+
else:
|
| 76 |
+
pe = torch.zeros(seq_len, self.d_model, device=x.device)
|
| 77 |
+
position = torch.arange(0, seq_len, dtype=torch.float, device=x.device).unsqueeze(1)
|
| 78 |
+
div_term = torch.exp(torch.arange(0, self.d_model - (self.d_model % 2), 2, dtype=torch.float,
|
| 79 |
+
device=x.device) * (-math.log(10000.0) / self.d_model))
|
| 80 |
+
|
| 81 |
+
pe[:, 0::2] = torch.sin(position * div_term)
|
| 82 |
+
pe[:, 1::2] = torch.cos(position * div_term[:self.d_model // 2 + 1] if self.d_model % 2 else div_term)
|
| 83 |
+
|
| 84 |
+
pe = pe.unsqueeze(0)
|
| 85 |
+
|
| 86 |
+
x = x + pe
|
| 87 |
+
return self.dropout(x)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class ThoughtEncoder(nn.Module):
|
| 91 |
+
"""Encodes text into thought vectors, representing abstract ideas or summaries.
|
| 92 |
+
|
| 93 |
+
Args:
|
| 94 |
+
vocab_size (int): Size of the vocabulary for token embeddings.
|
| 95 |
+
d_model (int, optional): Dimensionality of the model embeddings. Defaults to 256.
|
| 96 |
+
max_thoughts (int, optional): Maximum number of thought vectors to generate. Defaults to 16.
|
| 97 |
+
nhead (int, optional): Number of attention heads in the transformer encoder. Defaults to 8.
|
| 98 |
+
num_layers (int, optional): Number of transformer encoder layers. Defaults to 2.
|
| 99 |
+
dropout (float, optional): Dropout probability. Defaults to 0.1.
|
| 100 |
+
pad_id (int, optional): ID for padding tokens. Defaults to 0.
|
| 101 |
+
termination_threshold (float, optional): Threshold for stopping thought vector generation.
|
| 102 |
+
Defaults to 0.75.
|
| 103 |
+
"""
|
| 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
|
| 111 |
+
self.max_thoughts = max_thoughts
|
| 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)
|
| 119 |
+
self.thought_rnn = nn.GRU(d_model, d_model, batch_first=True)
|
| 120 |
+
self.fc_thought = nn.Linear(2 * d_model, d_model)
|
| 121 |
+
self.fc_terminate = nn.Linear(d_model, 1)
|
| 122 |
+
|
| 123 |
+
def forward(self, input_tokens: torch.Tensor, force_single_vector: bool = False) -> torch.Tensor:
|
| 124 |
+
"""Encodes input tokens into a sequence of thought vectors.
|
| 125 |
+
|
| 126 |
+
Args:
|
| 127 |
+
input_tokens (torch.Tensor): Token IDs, shape (batch_size, seq_len).
|
| 128 |
+
force_single_vector (bool, optional): If True, generates only one thought vector.
|
| 129 |
+
Defaults to False.
|
| 130 |
+
|
| 131 |
+
Returns:
|
| 132 |
+
torch.Tensor: Thought vectors, shape (batch_size, num_thoughts, d_model).
|
| 133 |
+
"""
|
| 134 |
+
batch_size, seq_len = input_tokens.size()
|
| 135 |
+
max_thoughts = 1 if force_single_vector else self.max_thoughts
|
| 136 |
+
|
| 137 |
+
src_key_padding_mask = (input_tokens == self.pad_id)
|
| 138 |
+
x = self.embedding(input_tokens)
|
| 139 |
+
x = self.positional_encoding(x)
|
| 140 |
+
encoded_text = self.text_encoder(x, src_key_padding_mask=src_key_padding_mask)
|
| 141 |
+
|
| 142 |
+
valid_mask = (~src_key_padding_mask).unsqueeze(-1).float()
|
| 143 |
+
text_context = (encoded_text * valid_mask).sum(dim=1) / valid_mask.sum(dim=1).clamp(min=1.0)
|
| 144 |
+
|
| 145 |
+
thought_vectors_list = []
|
| 146 |
+
thought_hidden = text_context.unsqueeze(0)
|
| 147 |
+
finished = torch.zeros(batch_size, dtype=torch.bool, device=input_tokens.device)
|
| 148 |
+
|
| 149 |
+
for t in range(max_thoughts):
|
| 150 |
+
if t > 0:
|
| 151 |
+
prev_thoughts = torch.stack(thought_vectors_list, dim=1)
|
| 152 |
+
attn_output, _ = self.thought_attention(prev_thoughts, prev_thoughts, prev_thoughts)
|
| 153 |
+
prev_context = attn_output.mean(dim=1)
|
| 154 |
+
else:
|
| 155 |
+
prev_context = torch.zeros_like(text_context)
|
| 156 |
+
|
| 157 |
+
combined = torch.cat([text_context, prev_context], dim=1)
|
| 158 |
+
next_thought = self.fc_thought(combined)
|
| 159 |
+
|
| 160 |
+
thought_hidden = self.thought_rnn(next_thought.unsqueeze(1), thought_hidden)[1]
|
| 161 |
+
termination_logit = self.fc_terminate(thought_hidden.squeeze(0))
|
| 162 |
+
termination_score = torch.sigmoid(termination_logit).squeeze(-1)
|
| 163 |
+
|
| 164 |
+
thought_vectors_list.append(next_thought)
|
| 165 |
+
finished = finished | (termination_score > self.termination_threshold)
|
| 166 |
+
if finished.all():
|
| 167 |
+
break
|
| 168 |
+
|
| 169 |
+
return torch.stack(thought_vectors_list, dim=1)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
class ThoughtDecoder(nn.Module):
|
| 173 |
+
"""Decodes thought vectors back into token sequences using a transformer decoder.
|
| 174 |
+
|
| 175 |
+
Args:
|
| 176 |
+
vocab_size (int): Size of the vocabulary for token embeddings.
|
| 177 |
+
d_model (int, optional): Dimensionality of the model embeddings. Defaults to 256.
|
| 178 |
+
num_layers (int, optional): Number of transformer decoder layers. Defaults to 2.
|
| 179 |
+
nhead (int, optional): Number of attention heads in the transformer decoder. Defaults to 8.
|
| 180 |
+
dropout (float, optional): Dropout probability. Defaults to 0.1.
|
| 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)
|
| 193 |
+
|
| 194 |
+
def generate_square_subsequent_mask(self, sz: int, device: torch.device) -> torch.Tensor:
|
| 195 |
+
"""Generates a causal mask for autoregressive decoding.
|
| 196 |
+
|
| 197 |
+
Args:
|
| 198 |
+
sz (int): Size of the mask (sequence length).
|
| 199 |
+
device (torch.device): Device to create the mask on.
|
| 200 |
+
|
| 201 |
+
Returns:
|
| 202 |
+
torch.Tensor: Mask tensor, shape (sz, sz).
|
| 203 |
+
"""
|
| 204 |
+
mask = (torch.triu(torch.ones(sz, sz, device=device)) == 1).transpose(0, 1)
|
| 205 |
+
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
|
| 206 |
+
return mask
|
| 207 |
+
|
| 208 |
+
def forward(self, thought_vectors: torch.Tensor, target_tokens: torch.Tensor) -> torch.Tensor:
|
| 209 |
+
"""Decodes thought vectors into token logits.
|
| 210 |
+
|
| 211 |
+
Args:
|
| 212 |
+
thought_vectors (torch.Tensor): Thought vectors, shape (batch_size, num_thoughts, d_model).
|
| 213 |
+
target_tokens (torch.Tensor): Target token IDs, shape (batch_size, tgt_seq_len).
|
| 214 |
+
|
| 215 |
+
Returns:
|
| 216 |
+
torch.Tensor: Logits over vocabulary, shape (batch_size, tgt_seq_len, vocab_size).
|
| 217 |
+
"""
|
| 218 |
+
tgt_emb = self.embedding(target_tokens)
|
| 219 |
+
tgt_emb = self.positional_encoding(tgt_emb)
|
| 220 |
+
tgt_mask = self.generate_square_subsequent_mask(tgt_emb.size(1), tgt_emb.device)
|
| 221 |
+
output = self.transformer_decoder(tgt=tgt_emb, memory=thought_vectors, tgt_mask=tgt_mask)
|
| 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 |
+
|
| 252 |
+
Args:
|
| 253 |
+
batch (List[List[List[int]]]): Batch of tokenized groups.
|
| 254 |
+
|
| 255 |
+
Returns:
|
| 256 |
+
List[torch.Tensor]: Padded tensors for each group, shape (num_sentences, max_seq_len).
|
| 257 |
+
"""
|
| 258 |
+
collated_groups = []
|
| 259 |
+
for group in batch:
|
| 260 |
+
if not group:
|
| 261 |
+
collated_groups.append(torch.tensor([]))
|
| 262 |
+
continue
|
| 263 |
+
group_tensors = [torch.tensor(seq, dtype=torch.long) for seq in group]
|
| 264 |
+
padded_group = nn.utils.rnn.pad_sequence(group_tensors, batch_first=True, padding_value=0)
|
| 265 |
+
collated_groups.append(padded_group)
|
| 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 |
+
|
| 272 |
+
Attributes:
|
| 273 |
+
device (torch.device): Computation device (CUDA if available, else CPU).
|
| 274 |
+
encoder (ThoughtEncoder): Encoder module.
|
| 275 |
+
decoder (ThoughtDecoder): Decoder module.
|
| 276 |
+
sp (spm.SentencePieceProcessor): SentencePiece processor.
|
| 277 |
+
"""
|
| 278 |
+
|
| 279 |
+
def __init__(self):
|
| 280 |
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 281 |
+
self.encoder = None
|
| 282 |
+
self.decoder = None
|
| 283 |
+
self.vocab_size = None
|
| 284 |
+
self.sp = None
|
| 285 |
+
self.d_model = None
|
| 286 |
+
self.max_thoughts = None
|
| 287 |
+
self.encoder_nhead = None
|
| 288 |
+
self.decoder_nhead = None
|
| 289 |
+
self.encoder_layers = None
|
| 290 |
+
self.decoder_layers = None
|
| 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
|
| 349 |
+
self.decoder_nhead = decoder_nhead
|
| 350 |
+
self.encoder_layers = encoder_layers
|
| 351 |
+
self.decoder_layers = decoder_layers
|
| 352 |
+
self.dropout = dropout
|
| 353 |
+
self.max_len = max_len
|
| 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
|
| 506 |
+
patience_counter = 0
|
| 507 |
+
self.save(save_path, spm_model_prefix, clean=False)
|
| 508 |
+
else:
|
| 509 |
+
patience_counter += 1
|
| 510 |
+
if patience_counter >= patience:
|
| 511 |
+
print(f"Early stopping triggered after {epoch + 1} epochs.")
|
| 512 |
+
break
|
| 513 |
+
|
| 514 |
+
scheduler.step()
|
| 515 |
+
|
| 516 |
+
print(f"Best model saved to {save_path}")
|
| 517 |
+
|
| 518 |
+
def encode(self, text: Union[str, List[str]], force_single_vector: bool = False) -> torch.Tensor:
|
| 519 |
+
"""Encodes text into thought vectors.
|
| 520 |
+
|
| 521 |
+
Args:
|
| 522 |
+
text (Union[str, List[str]]): Input text as a string or list of strings.
|
| 523 |
+
force_single_vector (bool, optional): If True, generates only one thought vector.
|
| 524 |
+
Defaults to False.
|
| 525 |
+
|
| 526 |
+
Returns:
|
| 527 |
+
torch.Tensor: Thought vectors, shape (batch_size, num_thoughts, d_model).
|
| 528 |
+
"""
|
| 529 |
+
if self.encoder is None or self.sp is None:
|
| 530 |
+
raise RuntimeError("Model not trained or loaded.")
|
| 531 |
+
|
| 532 |
+
if isinstance(text, str):
|
| 533 |
+
text = [text]
|
| 534 |
+
|
| 535 |
+
tokenized = [[self.sp.bos_id()] + self.sp.EncodeAsIds(t) + [self.sp.eos_id()] for t in text]
|
| 536 |
+
input_tokens = nn.utils.rnn.pad_sequence(
|
| 537 |
+
[torch.tensor(seq, dtype=torch.long) for seq in tokenized],
|
| 538 |
+
batch_first=True, padding_value=self.sp.pad_id()
|
| 539 |
+
).to(self.device)
|
| 540 |
+
|
| 541 |
+
return self.encoder(input_tokens, force_single_vector)
|
| 542 |
+
|
| 543 |
+
def decode(self, thought_vectors: torch.Tensor, max_length: int = 50, beam_width: Optional[int] = 5,
|
| 544 |
+
temperature: float = 1.0) -> List[str]:
|
| 545 |
+
"""Decodes thought vectors into text sequences.
|
| 546 |
+
|
| 547 |
+
Args:
|
| 548 |
+
thought_vectors (torch.Tensor): Thought vectors, shape (batch_size, num_thoughts, d_model).
|
| 549 |
+
max_length (int, optional): Maximum length of generated sequences. Defaults to 50.
|
| 550 |
+
beam_width (Optional[int], optional): Beam width for beam search; if None, uses greedy
|
| 551 |
+
decoding. Defaults to 5.
|
| 552 |
+
temperature (float, optional): Temperature for softmax sampling in greedy decoding.
|
| 553 |
+
Defaults to 1.0.
|
| 554 |
+
|
| 555 |
+
Returns:
|
| 556 |
+
List[str]: Decoded text sequences.
|
| 557 |
+
"""
|
| 558 |
+
if self.decoder is None or self.sp is None:
|
| 559 |
+
raise RuntimeError("Model not trained or loaded.")
|
| 560 |
+
|
| 561 |
+
thought_vectors = thought_vectors.to(self.device)
|
| 562 |
+
batch_size = thought_vectors.size(0)
|
| 563 |
+
|
| 564 |
+
if beam_width is not None and beam_width > 1:
|
| 565 |
+
return self._beam_search_decode(thought_vectors, max_length, beam_width)
|
| 566 |
+
|
| 567 |
+
target_tokens = torch.full((batch_size, 1), self.sp.bos_id(), dtype=torch.long, device=self.device)
|
| 568 |
+
for _ in range(max_length - 1):
|
| 569 |
+
logits = self.decoder(thought_vectors, target_tokens)
|
| 570 |
+
logits = logits[:, -1, :] / temperature
|
| 571 |
+
next_token = logits.softmax(dim=-1).multinomial(1)
|
| 572 |
+
target_tokens = torch.cat([target_tokens, next_token], dim=1)
|
| 573 |
+
if (next_token == self.sp.eos_id()).all():
|
| 574 |
+
break
|
| 575 |
+
|
| 576 |
+
return [self.sp.DecodeIds(seq.tolist()) for seq in target_tokens]
|
| 577 |
+
|
| 578 |
+
def _beam_search_decode(self, thought_vectors: torch.Tensor, max_length: int, beam_width: int) -> List[str]:
|
| 579 |
+
"""Performs beam search decoding of thought vectors.
|
| 580 |
+
|
| 581 |
+
Args:
|
| 582 |
+
thought_vectors (torch.Tensor): Thought vectors, shape (batch_size, num_thoughts, d_model).
|
| 583 |
+
max_length (int): Maximum length of generated sequences.
|
| 584 |
+
beam_width (int): Number of beams to maintain during search.
|
| 585 |
+
|
| 586 |
+
Returns:
|
| 587 |
+
List[str]: Decoded text sequences, one per batch item.
|
| 588 |
+
"""
|
| 589 |
+
batch_size = thought_vectors.size(0)
|
| 590 |
+
start_tokens = torch.full((batch_size, 1), self.sp.bos_id(), dtype=torch.long, device=self.device)
|
| 591 |
+
beams = [[] for _ in range(batch_size)]
|
| 592 |
+
finished = [[] for _ in range(batch_size)]
|
| 593 |
+
|
| 594 |
+
for i in range(batch_size):
|
| 595 |
+
beams[i].append((0.0, start_tokens[i].unsqueeze(0)))
|
| 596 |
+
|
| 597 |
+
for _ in range(max_length - 1):
|
| 598 |
+
new_beams = [[] for _ in range(batch_size)]
|
| 599 |
+
for i in range(batch_size):
|
| 600 |
+
for score, seq in beams[i]:
|
| 601 |
+
if seq[:, -1].item() == self.sp.eos_id():
|
| 602 |
+
finished[i].append((score, seq))
|
| 603 |
+
continue
|
| 604 |
+
logits = self.decoder(thought_vectors[i:i + 1], seq)[:, -1, :]
|
| 605 |
+
probs, next_tokens = logits.softmax(dim=-1).topk(beam_width, dim=-1)
|
| 606 |
+
for p, t in zip(probs[0], next_tokens[0]):
|
| 607 |
+
new_score = score - p.log().item()
|
| 608 |
+
new_seq = torch.cat([seq, t.unsqueeze(0).unsqueeze(-1)], dim=1)
|
| 609 |
+
new_beams[i].append((new_score, new_seq))
|
| 610 |
+
|
| 611 |
+
beams[i] = sorted(new_beams[i], key=lambda x: x[0])[:beam_width]
|
| 612 |
+
|
| 613 |
+
if all(len(finished[i]) >= beam_width for i in range(batch_size)):
|
| 614 |
+
break
|
| 615 |
+
|
| 616 |
+
results = []
|
| 617 |
+
for i in range(batch_size):
|
| 618 |
+
combined = sorted(finished[i] + beams[i], key=lambda x: x[0])
|
| 619 |
+
results.append(self.sp.DecodeIds(combined[0][1].squeeze(0).tolist()) if combined else "<empty>")
|
| 620 |
+
|
| 621 |
+
return results
|
| 622 |
+
|
| 623 |
+
def save(self, path: str, spm_model_prefix: str, clean: bool = True) -> None:
|
| 624 |
+
"""Saves the model and SentencePiece data to a tar file.
|
| 625 |
+
|
| 626 |
+
Args:
|
| 627 |
+
path (str): Path to save the tar file.
|
| 628 |
+
spm_model_prefix (str): Prefix for SentencePiece model files.
|
| 629 |
+
clean (bool, optional): If True, removes temporary files after saving. Defaults to True.
|
| 630 |
+
"""
|
| 631 |
+
if self.encoder is None or self.decoder is None:
|
| 632 |
+
raise RuntimeError("No model to save.")
|
| 633 |
+
|
| 634 |
+
if not os.path.exists(f"{spm_model_prefix}.model") or not os.path.exists(f"{spm_model_prefix}.vocab"):
|
| 635 |
+
raise FileNotFoundError(f"SentencePiece files ({spm_model_prefix}.model or .vocab) not found.")
|
| 636 |
+
|
| 637 |
+
with tarfile.open(path, "w") as tar:
|
| 638 |
+
torch.save({
|
| 639 |
+
'encoder_state_dict': self.encoder.state_dict(),
|
| 640 |
+
'decoder_state_dict': self.decoder.state_dict(),
|
| 641 |
+
'vocab_size': self.vocab_size,
|
| 642 |
+
'd_model': self.d_model,
|
| 643 |
+
'max_thoughts': self.max_thoughts,
|
| 644 |
+
'encoder_nhead': self.encoder_nhead,
|
| 645 |
+
'decoder_nhead': self.decoder_nhead,
|
| 646 |
+
'encoder_layers': self.encoder_layers,
|
| 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")
|
| 654 |
+
tar.add(f"{spm_model_prefix}.vocab")
|
| 655 |
+
|
| 656 |
+
if clean:
|
| 657 |
+
os.remove("model.pth")
|
| 658 |
+
os.remove(f"{spm_model_prefix}.model")
|
| 659 |
+
os.remove(f"{spm_model_prefix}.vocab")
|
| 660 |
+
|
| 661 |
+
@classmethod
|
| 662 |
+
def load(cls, path: str) -> 'ThoughtVectors':
|
| 663 |
+
"""Loads a trained model from a tar file.
|
| 664 |
+
|
| 665 |
+
Args:
|
| 666 |
+
path (str): Path to the tar file containing the model.
|
| 667 |
+
|
| 668 |
+
Returns:
|
| 669 |
+
ThoughtVectors: Loaded model instance.
|
| 670 |
+
"""
|
| 671 |
+
translator = cls()
|
| 672 |
+
try:
|
| 673 |
+
with tarfile.open(path, "r") as tar:
|
| 674 |
+
tar.extractall()
|
| 675 |
+
checkpoint = torch.load("model.pth")
|
| 676 |
+
translator.sp = spm.SentencePieceProcessor()
|
| 677 |
+
if not translator.sp.Load("spm.model"):
|
| 678 |
+
raise RuntimeError("Failed to load SentencePiece model.")
|
| 679 |
+
finally:
|
| 680 |
+
for f in ["model.pth", "spm.model", "spm.vocab"]:
|
| 681 |
+
if os.path.exists(f):
|
| 682 |
+
os.remove(f)
|
| 683 |
+
|
| 684 |
+
translator.vocab_size = checkpoint['vocab_size']
|
| 685 |
+
translator.d_model = checkpoint['d_model']
|
| 686 |
+
translator.max_thoughts = checkpoint['max_thoughts']
|
| 687 |
+
translator.encoder_nhead = checkpoint['encoder_nhead']
|
| 688 |
+
translator.decoder_nhead = checkpoint['decoder_nhead']
|
| 689 |
+
translator.encoder_layers = checkpoint['encoder_layers']
|
| 690 |
+
translator.decoder_layers = checkpoint['decoder_layers']
|
| 691 |
+
translator.dropout = checkpoint['dropout']
|
| 692 |
+
translator.max_len = checkpoint['max_len']
|
| 693 |
+
translator.termination_threshold = checkpoint['termination_threshold']
|
| 694 |
+
|
| 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'])
|
| 706 |
+
translator.decoder.load_state_dict(checkpoint['decoder_state_dict'])
|
| 707 |
+
|
| 708 |
+
return translator
|
| 709 |
+
|
| 710 |
+
|
| 711 |
+
# Example usage with a shorter dataset
|
| 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]]
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
data = data_generator("data.csv")
|
| 727 |
+
|
| 728 |
+
# Define the 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 |
+
|