mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
942 lines
38 KiB
Markdown
942 lines
38 KiB
Markdown
# 6. Ön Eğitim ve Modellerin Yüklenmesi
|
||
|
||
## Metin Üretimi
|
||
|
||
Bir modeli eğitmek için, o modelin yeni token'lar üretebilmesi gerekecek. Ardından, üretilen token'ları beklenenlerle karşılaştırarak modeli **gerekli token'ları öğrenmesi için eğiteceğiz**.
|
||
|
||
Önceki örneklerde bazı token'ları zaten tahmin ettiğimiz için, bu amaçla o fonksiyonu yeniden kullanmak mümkündür.
|
||
|
||
> [!TIP]
|
||
> Bu altıncı aşamanın amacı çok basit: **Modeli sıfırdan eğitmek**. Bunun için önceki LLM mimarisi, tanımlı kayıp fonksiyonları ve optimizasyon kullanarak veri setleri üzerinde döngülerle tüm model parametrelerini eğitmek için kullanılacaktır.
|
||
|
||
## Metin Değerlendirmesi
|
||
|
||
Doğru bir eğitim gerçekleştirmek için, beklenen token için elde edilen tahminleri ölçmek gereklidir. Eğitimin amacı, doğru token'ın olasılığını maksimize etmektir; bu, diğer token'lara göre olasılığını artırmayı içerir.
|
||
|
||
Doğru token'ın olasılığını maksimize etmek için, modelin ağırlıkları, bu olasılığın maksimize edilmesi için değiştirilmelidir. Ağırlık güncellemeleri **geri yayılım** yoluyla yapılır. Bu, **maksimize edilecek bir kayıp fonksiyonu** gerektirir. Bu durumda, fonksiyon **gerçekleştirilen tahmin ile istenen arasındaki fark** olacaktır.
|
||
|
||
Ancak, ham tahminlerle çalışmak yerine, n tabanlı bir logaritma ile çalışacaktır. Yani, beklenen token'ın mevcut tahmini 7.4541e-05 ise, **7.4541e-05**'in doğal logaritması (taban *e*) yaklaşık olarak **-9.5042**'dir.\
|
||
Örneğin, 5 token'lık bir bağlam uzunluğuna sahip her giriş için modelin 5 token tahmin etmesi gerekecek; ilk 4 token, girdinin sonuncusu ve beşincisi tahmin edilen olacaktır. Bu nedenle, her giriş için bu durumda 5 tahminimiz olacak (ilk 4'ü girdi olsa da model bunu bilmez) ve dolayısıyla 5 beklenen token ve 5 maksimize edilecek olasılık olacaktır.
|
||
|
||
Bu nedenle, her tahmine doğal logaritma uygulandıktan sonra, **ortalama** hesaplanır, **eksi işareti kaldırılır** (bu _çapraz entropi kaybı_ olarak adlandırılır) ve bu, **0'a mümkün olduğunca yakın bir şekilde azaltılması gereken sayıdır** çünkü 1'in doğal logaritması 0'dır:
|
||
|
||
<figure><img src="../../images/image (10) (1).png" alt="" width="563"><figcaption><p><a href="https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233">https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233</a></p></figcaption></figure>
|
||
|
||
Modelin ne kadar iyi olduğunu ölçmenin bir diğer yolu da karmaşıklıktır. **Karmaşıklık**, bir olasılık modelinin bir örneği ne kadar iyi tahmin ettiğini değerlendirmek için kullanılan bir metriktir. Dil modellemesinde, bir dizideki bir sonraki token'ı tahmin ederken **modelin belirsizliğini** temsil eder.\
|
||
Örneğin, 48725 karmaşıklık değeri, bir token'ı tahmin etmesi gerektiğinde, kelime dağarcığındaki 48,725 token'dan hangisinin doğru olduğu konusunda emin olmadığını gösterir.
|
||
|
||
## Ön Eğitim Örneği
|
||
|
||
Bu, [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb) adresinde önerilen başlangıç kodudur, bazen hafifçe değiştirilmiştir.
|
||
|
||
<details>
|
||
|
||
<summary>Burada kullanılan önceki kod ama önceki bölümlerde zaten açıklandı</summary>
|
||
```python
|
||
"""
|
||
This is code explained before so it won't be exaplained
|
||
"""
|
||
|
||
import tiktoken
|
||
import torch
|
||
import torch.nn as nn
|
||
from torch.utils.data import Dataset, DataLoader
|
||
|
||
|
||
class GPTDatasetV1(Dataset):
|
||
def __init__(self, txt, tokenizer, max_length, stride):
|
||
self.input_ids = []
|
||
self.target_ids = []
|
||
|
||
# Tokenize the entire text
|
||
token_ids = tokenizer.encode(txt, allowed_special={"<|endoftext|>"})
|
||
|
||
# Use a sliding window to chunk the book into overlapping sequences of max_length
|
||
for i in range(0, len(token_ids) - max_length, stride):
|
||
input_chunk = token_ids[i:i + max_length]
|
||
target_chunk = token_ids[i + 1: i + max_length + 1]
|
||
self.input_ids.append(torch.tensor(input_chunk))
|
||
self.target_ids.append(torch.tensor(target_chunk))
|
||
|
||
def __len__(self):
|
||
return len(self.input_ids)
|
||
|
||
def __getitem__(self, idx):
|
||
return self.input_ids[idx], self.target_ids[idx]
|
||
|
||
|
||
def create_dataloader_v1(txt, batch_size=4, max_length=256,
|
||
stride=128, shuffle=True, drop_last=True, num_workers=0):
|
||
# Initialize the tokenizer
|
||
tokenizer = tiktoken.get_encoding("gpt2")
|
||
|
||
# Create dataset
|
||
dataset = GPTDatasetV1(txt, tokenizer, max_length, stride)
|
||
|
||
# Create dataloader
|
||
dataloader = DataLoader(
|
||
dataset, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last, num_workers=num_workers)
|
||
|
||
return dataloader
|
||
|
||
|
||
class MultiHeadAttention(nn.Module):
|
||
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
|
||
super().__init__()
|
||
assert d_out % num_heads == 0, "d_out must be divisible by n_heads"
|
||
|
||
self.d_out = d_out
|
||
self.num_heads = num_heads
|
||
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
|
||
|
||
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
|
||
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
|
||
self.dropout = nn.Dropout(dropout)
|
||
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1))
|
||
|
||
def forward(self, x):
|
||
b, num_tokens, d_in = x.shape
|
||
|
||
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
|
||
queries = self.W_query(x)
|
||
values = self.W_value(x)
|
||
|
||
# We implicitly split the matrix by adding a `num_heads` dimension
|
||
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
|
||
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
|
||
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
|
||
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
|
||
|
||
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
|
||
keys = keys.transpose(1, 2)
|
||
queries = queries.transpose(1, 2)
|
||
values = values.transpose(1, 2)
|
||
|
||
# Compute scaled dot-product attention (aka self-attention) with a causal mask
|
||
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
|
||
|
||
# Original mask truncated to the number of tokens and converted to boolean
|
||
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
|
||
|
||
# Use the mask to fill attention scores
|
||
attn_scores.masked_fill_(mask_bool, -torch.inf)
|
||
|
||
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
|
||
attn_weights = self.dropout(attn_weights)
|
||
|
||
# Shape: (b, num_tokens, num_heads, head_dim)
|
||
context_vec = (attn_weights @ values).transpose(1, 2)
|
||
|
||
# Combine heads, where self.d_out = self.num_heads * self.head_dim
|
||
context_vec = context_vec.reshape(b, num_tokens, self.d_out)
|
||
context_vec = self.out_proj(context_vec) # optional projection
|
||
|
||
return context_vec
|
||
|
||
|
||
class LayerNorm(nn.Module):
|
||
def __init__(self, emb_dim):
|
||
super().__init__()
|
||
self.eps = 1e-5
|
||
self.scale = nn.Parameter(torch.ones(emb_dim))
|
||
self.shift = nn.Parameter(torch.zeros(emb_dim))
|
||
|
||
def forward(self, x):
|
||
mean = x.mean(dim=-1, keepdim=True)
|
||
var = x.var(dim=-1, keepdim=True, unbiased=False)
|
||
norm_x = (x - mean) / torch.sqrt(var + self.eps)
|
||
return self.scale * norm_x + self.shift
|
||
|
||
|
||
class GELU(nn.Module):
|
||
def __init__(self):
|
||
super().__init__()
|
||
|
||
def forward(self, x):
|
||
return 0.5 * x * (1 + torch.tanh(
|
||
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
|
||
(x + 0.044715 * torch.pow(x, 3))
|
||
))
|
||
|
||
|
||
class FeedForward(nn.Module):
|
||
def __init__(self, cfg):
|
||
super().__init__()
|
||
self.layers = nn.Sequential(
|
||
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
|
||
GELU(),
|
||
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
|
||
)
|
||
|
||
def forward(self, x):
|
||
return self.layers(x)
|
||
|
||
|
||
class TransformerBlock(nn.Module):
|
||
def __init__(self, cfg):
|
||
super().__init__()
|
||
self.att = MultiHeadAttention(
|
||
d_in=cfg["emb_dim"],
|
||
d_out=cfg["emb_dim"],
|
||
context_length=cfg["context_length"],
|
||
num_heads=cfg["n_heads"],
|
||
dropout=cfg["drop_rate"],
|
||
qkv_bias=cfg["qkv_bias"])
|
||
self.ff = FeedForward(cfg)
|
||
self.norm1 = LayerNorm(cfg["emb_dim"])
|
||
self.norm2 = LayerNorm(cfg["emb_dim"])
|
||
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
|
||
|
||
def forward(self, x):
|
||
# Shortcut connection for attention block
|
||
shortcut = x
|
||
x = self.norm1(x)
|
||
x = self.att(x) # Shape [batch_size, num_tokens, emb_size]
|
||
x = self.drop_shortcut(x)
|
||
x = x + shortcut # Add the original input back
|
||
|
||
# Shortcut connection for feed-forward block
|
||
shortcut = x
|
||
x = self.norm2(x)
|
||
x = self.ff(x)
|
||
x = self.drop_shortcut(x)
|
||
x = x + shortcut # Add the original input back
|
||
|
||
return x
|
||
|
||
|
||
class GPTModel(nn.Module):
|
||
def __init__(self, cfg):
|
||
super().__init__()
|
||
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
|
||
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
|
||
self.drop_emb = nn.Dropout(cfg["drop_rate"])
|
||
|
||
self.trf_blocks = nn.Sequential(
|
||
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])])
|
||
|
||
self.final_norm = LayerNorm(cfg["emb_dim"])
|
||
self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)
|
||
|
||
def forward(self, in_idx):
|
||
batch_size, seq_len = in_idx.shape
|
||
tok_embeds = self.tok_emb(in_idx)
|
||
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
|
||
x = tok_embeds + pos_embeds # Shape [batch_size, num_tokens, emb_size]
|
||
x = self.drop_emb(x)
|
||
x = self.trf_blocks(x)
|
||
x = self.final_norm(x)
|
||
logits = self.out_head(x)
|
||
return logits
|
||
```
|
||
</details>
|
||
```python
|
||
# Download contents to train the data with
|
||
import os
|
||
import urllib.request
|
||
|
||
file_path = "the-verdict.txt"
|
||
url = "https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt"
|
||
|
||
if not os.path.exists(file_path):
|
||
with urllib.request.urlopen(url) as response:
|
||
text_data = response.read().decode('utf-8')
|
||
with open(file_path, "w", encoding="utf-8") as file:
|
||
file.write(text_data)
|
||
else:
|
||
with open(file_path, "r", encoding="utf-8") as file:
|
||
text_data = file.read()
|
||
|
||
total_characters = len(text_data)
|
||
tokenizer = tiktoken.get_encoding("gpt2")
|
||
total_tokens = len(tokenizer.encode(text_data))
|
||
|
||
print("Data downloaded")
|
||
print("Characters:", total_characters)
|
||
print("Tokens:", total_tokens)
|
||
|
||
# Model initialization
|
||
GPT_CONFIG_124M = {
|
||
"vocab_size": 50257, # Vocabulary size
|
||
"context_length": 256, # Shortened context length (orig: 1024)
|
||
"emb_dim": 768, # Embedding dimension
|
||
"n_heads": 12, # Number of attention heads
|
||
"n_layers": 12, # Number of layers
|
||
"drop_rate": 0.1, # Dropout rate
|
||
"qkv_bias": False # Query-key-value bias
|
||
}
|
||
|
||
torch.manual_seed(123)
|
||
model = GPTModel(GPT_CONFIG_124M)
|
||
model.eval()
|
||
print ("Model initialized")
|
||
|
||
|
||
# Functions to transform from tokens to ids and from to ids to tokens
|
||
def text_to_token_ids(text, tokenizer):
|
||
encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
|
||
encoded_tensor = torch.tensor(encoded).unsqueeze(0) # add batch dimension
|
||
return encoded_tensor
|
||
|
||
def token_ids_to_text(token_ids, tokenizer):
|
||
flat = token_ids.squeeze(0) # remove batch dimension
|
||
return tokenizer.decode(flat.tolist())
|
||
|
||
|
||
|
||
# Define loss functions
|
||
def calc_loss_batch(input_batch, target_batch, model, device):
|
||
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
|
||
logits = model(input_batch)
|
||
loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())
|
||
return loss
|
||
|
||
|
||
def calc_loss_loader(data_loader, model, device, num_batches=None):
|
||
total_loss = 0.
|
||
if len(data_loader) == 0:
|
||
return float("nan")
|
||
elif num_batches is None:
|
||
num_batches = len(data_loader)
|
||
else:
|
||
# Reduce the number of batches to match the total number of batches in the data loader
|
||
# if num_batches exceeds the number of batches in the data loader
|
||
num_batches = min(num_batches, len(data_loader))
|
||
for i, (input_batch, target_batch) in enumerate(data_loader):
|
||
if i < num_batches:
|
||
loss = calc_loss_batch(input_batch, target_batch, model, device)
|
||
total_loss += loss.item()
|
||
else:
|
||
break
|
||
return total_loss / num_batches
|
||
|
||
|
||
# Apply Train/validation ratio and create dataloaders
|
||
train_ratio = 0.90
|
||
split_idx = int(train_ratio * len(text_data))
|
||
train_data = text_data[:split_idx]
|
||
val_data = text_data[split_idx:]
|
||
|
||
torch.manual_seed(123)
|
||
|
||
train_loader = create_dataloader_v1(
|
||
train_data,
|
||
batch_size=2,
|
||
max_length=GPT_CONFIG_124M["context_length"],
|
||
stride=GPT_CONFIG_124M["context_length"],
|
||
drop_last=True,
|
||
shuffle=True,
|
||
num_workers=0
|
||
)
|
||
|
||
val_loader = create_dataloader_v1(
|
||
val_data,
|
||
batch_size=2,
|
||
max_length=GPT_CONFIG_124M["context_length"],
|
||
stride=GPT_CONFIG_124M["context_length"],
|
||
drop_last=False,
|
||
shuffle=False,
|
||
num_workers=0
|
||
)
|
||
|
||
|
||
# Sanity checks
|
||
if total_tokens * (train_ratio) < GPT_CONFIG_124M["context_length"]:
|
||
print("Not enough tokens for the training loader. "
|
||
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
|
||
"increase the `training_ratio`")
|
||
|
||
if total_tokens * (1-train_ratio) < GPT_CONFIG_124M["context_length"]:
|
||
print("Not enough tokens for the validation loader. "
|
||
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
|
||
"decrease the `training_ratio`")
|
||
|
||
print("Train loader:")
|
||
for x, y in train_loader:
|
||
print(x.shape, y.shape)
|
||
|
||
print("\nValidation loader:")
|
||
for x, y in val_loader:
|
||
print(x.shape, y.shape)
|
||
|
||
train_tokens = 0
|
||
for input_batch, target_batch in train_loader:
|
||
train_tokens += input_batch.numel()
|
||
|
||
val_tokens = 0
|
||
for input_batch, target_batch in val_loader:
|
||
val_tokens += input_batch.numel()
|
||
|
||
print("Training tokens:", train_tokens)
|
||
print("Validation tokens:", val_tokens)
|
||
print("All tokens:", train_tokens + val_tokens)
|
||
|
||
|
||
# Indicate the device to use
|
||
if torch.cuda.is_available():
|
||
device = torch.device("cuda")
|
||
elif torch.backends.mps.is_available():
|
||
device = torch.device("mps")
|
||
else:
|
||
device = torch.device("cpu")
|
||
|
||
print(f"Using {device} device.")
|
||
|
||
model.to(device) # no assignment model = model.to(device) necessary for nn.Module classes
|
||
|
||
|
||
|
||
# Pre-calculate losses without starting yet
|
||
torch.manual_seed(123) # For reproducibility due to the shuffling in the data loader
|
||
|
||
with torch.no_grad(): # Disable gradient tracking for efficiency because we are not training, yet
|
||
train_loss = calc_loss_loader(train_loader, model, device)
|
||
val_loss = calc_loss_loader(val_loader, model, device)
|
||
|
||
print("Training loss:", train_loss)
|
||
print("Validation loss:", val_loss)
|
||
|
||
|
||
# Functions to train the data
|
||
def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,
|
||
eval_freq, eval_iter, start_context, tokenizer):
|
||
# Initialize lists to track losses and tokens seen
|
||
train_losses, val_losses, track_tokens_seen = [], [], []
|
||
tokens_seen, global_step = 0, -1
|
||
|
||
# Main training loop
|
||
for epoch in range(num_epochs):
|
||
model.train() # Set model to training mode
|
||
|
||
for input_batch, target_batch in train_loader:
|
||
optimizer.zero_grad() # Reset loss gradients from previous batch iteration
|
||
loss = calc_loss_batch(input_batch, target_batch, model, device)
|
||
loss.backward() # Calculate loss gradients
|
||
optimizer.step() # Update model weights using loss gradients
|
||
tokens_seen += input_batch.numel()
|
||
global_step += 1
|
||
|
||
# Optional evaluation step
|
||
if global_step % eval_freq == 0:
|
||
train_loss, val_loss = evaluate_model(
|
||
model, train_loader, val_loader, device, eval_iter)
|
||
train_losses.append(train_loss)
|
||
val_losses.append(val_loss)
|
||
track_tokens_seen.append(tokens_seen)
|
||
print(f"Ep {epoch+1} (Step {global_step:06d}): "
|
||
f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")
|
||
|
||
# Print a sample text after each epoch
|
||
generate_and_print_sample(
|
||
model, tokenizer, device, start_context
|
||
)
|
||
|
||
return train_losses, val_losses, track_tokens_seen
|
||
|
||
|
||
def evaluate_model(model, train_loader, val_loader, device, eval_iter):
|
||
model.eval()
|
||
with torch.no_grad():
|
||
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
|
||
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
|
||
model.train()
|
||
return train_loss, val_loss
|
||
|
||
|
||
def generate_and_print_sample(model, tokenizer, device, start_context):
|
||
model.eval()
|
||
context_size = model.pos_emb.weight.shape[0]
|
||
encoded = text_to_token_ids(start_context, tokenizer).to(device)
|
||
with torch.no_grad():
|
||
token_ids = generate_text(
|
||
model=model, idx=encoded,
|
||
max_new_tokens=50, context_size=context_size
|
||
)
|
||
decoded_text = token_ids_to_text(token_ids, tokenizer)
|
||
print(decoded_text.replace("\n", " ")) # Compact print format
|
||
model.train()
|
||
|
||
|
||
# Start training!
|
||
import time
|
||
start_time = time.time()
|
||
|
||
torch.manual_seed(123)
|
||
model = GPTModel(GPT_CONFIG_124M)
|
||
model.to(device)
|
||
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1)
|
||
|
||
num_epochs = 10
|
||
train_losses, val_losses, tokens_seen = train_model_simple(
|
||
model, train_loader, val_loader, optimizer, device,
|
||
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
|
||
start_context="Every effort moves you", tokenizer=tokenizer
|
||
)
|
||
|
||
end_time = time.time()
|
||
execution_time_minutes = (end_time - start_time) / 60
|
||
print(f"Training completed in {execution_time_minutes:.2f} minutes.")
|
||
|
||
|
||
|
||
# Show graphics with the training process
|
||
import matplotlib.pyplot as plt
|
||
from matplotlib.ticker import MaxNLocator
|
||
import math
|
||
def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):
|
||
fig, ax1 = plt.subplots(figsize=(5, 3))
|
||
ax1.plot(epochs_seen, train_losses, label="Training loss")
|
||
ax1.plot(
|
||
epochs_seen, val_losses, linestyle="-.", label="Validation loss"
|
||
)
|
||
ax1.set_xlabel("Epochs")
|
||
ax1.set_ylabel("Loss")
|
||
ax1.legend(loc="upper right")
|
||
ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
|
||
ax2 = ax1.twiny()
|
||
ax2.plot(tokens_seen, train_losses, alpha=0)
|
||
ax2.set_xlabel("Tokens seen")
|
||
fig.tight_layout()
|
||
plt.show()
|
||
|
||
# Compute perplexity from the loss values
|
||
train_ppls = [math.exp(loss) for loss in train_losses]
|
||
val_ppls = [math.exp(loss) for loss in val_losses]
|
||
# Plot perplexity over tokens seen
|
||
plt.figure()
|
||
plt.plot(tokens_seen, train_ppls, label='Training Perplexity')
|
||
plt.plot(tokens_seen, val_ppls, label='Validation Perplexity')
|
||
plt.xlabel('Tokens Seen')
|
||
plt.ylabel('Perplexity')
|
||
plt.title('Perplexity over Training')
|
||
plt.legend()
|
||
plt.show()
|
||
|
||
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
|
||
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
|
||
|
||
|
||
torch.save({
|
||
"model_state_dict": model.state_dict(),
|
||
"optimizer_state_dict": optimizer.state_dict(),
|
||
},
|
||
"/tmp/model_and_optimizer.pth"
|
||
)
|
||
```
|
||
### Metni <--> id'lere dönüştüren fonksiyonlar
|
||
|
||
Bu, kelime dağarcığındaki metinleri id'lere ve tersine dönüştürmek için kullanılabilecek bazı basit fonksiyonlardır. Bu, metin işleme sürecinin başında ve tahminlerin sonunda gereklidir:
|
||
```python
|
||
# Functions to transform from tokens to ids and from to ids to tokens
|
||
def text_to_token_ids(text, tokenizer):
|
||
encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
|
||
encoded_tensor = torch.tensor(encoded).unsqueeze(0) # add batch dimension
|
||
return encoded_tensor
|
||
|
||
def token_ids_to_text(token_ids, tokenizer):
|
||
flat = token_ids.squeeze(0) # remove batch dimension
|
||
return tokenizer.decode(flat.tolist())
|
||
```
|
||
### Metin oluşturma fonksiyonları
|
||
|
||
Önceki bölümde, **en olası token**'ı elde eden bir fonksiyon tanımlandı. Ancak bu, her giriş için her zaman aynı çıktının üretileceği anlamına gelir, bu da çok deterministik hale getirir.
|
||
|
||
Aşağıdaki `generate_text` fonksiyonu, `top-k`, `temperature` ve `multinomial` kavramlarını uygulayacaktır.
|
||
|
||
- **`top-k`**, en üst k token dışında tüm token'ların olasılıklarını `-inf`'ye düşürmeye başlayacağımız anlamına gelir. Yani, k=3 ise, bir karar vermeden önce yalnızca en olası 3 token'ın olasılığı `-inf`'den farklı olacaktır.
|
||
- **`temperature`**, her olasılığın sıcaklık değeri ile bölüneceği anlamına gelir. `0.1` değeri, en yüksek olasılığı en düşük olasılıkla karşılaştırarak artırırken, örneğin `5` sıcaklığı daha düz bir dağılım yaratır. Bu, LLM'nin yanıtlarındaki varyasyonu artırmaya yardımcı olur.
|
||
- Sıcaklık uygulandıktan sonra, tüm kalan token'ların toplam olasılığının 1 olması için tekrar bir **`softmax`** fonksiyonu uygulanır.
|
||
- Son olarak, en büyük olasılığa sahip token'ı seçmek yerine, fonksiyon **`multinomial`** uygulanarak **son olasılıklara göre bir sonraki token'ı tahmin eder**. Yani, token 1'in %70 olasılığı, token 2'nin %20 ve token 3'ün %10 olasılığı varsa, %70 oranında token 1 seçilecek, %20 oranında token 2 ve %10 oranında token 3 seçilecektir.
|
||
```python
|
||
# Generate text function
|
||
def generate_text(model, idx, max_new_tokens, context_size, temperature=0.0, top_k=None, eos_id=None):
|
||
|
||
# For-loop is the same as before: Get logits, and only focus on last time step
|
||
for _ in range(max_new_tokens):
|
||
idx_cond = idx[:, -context_size:]
|
||
with torch.no_grad():
|
||
logits = model(idx_cond)
|
||
logits = logits[:, -1, :]
|
||
|
||
# New: Filter logits with top_k sampling
|
||
if top_k is not None:
|
||
# Keep only top_k values
|
||
top_logits, _ = torch.topk(logits, top_k)
|
||
min_val = top_logits[:, -1]
|
||
logits = torch.where(logits < min_val, torch.tensor(float("-inf")).to(logits.device), logits)
|
||
|
||
# New: Apply temperature scaling
|
||
if temperature > 0.0:
|
||
logits = logits / temperature
|
||
|
||
# Apply softmax to get probabilities
|
||
probs = torch.softmax(logits, dim=-1) # (batch_size, context_len)
|
||
|
||
# Sample from the distribution
|
||
idx_next = torch.multinomial(probs, num_samples=1) # (batch_size, 1)
|
||
|
||
# Otherwise same as before: get idx of the vocab entry with the highest logits value
|
||
else:
|
||
idx_next = torch.argmax(logits, dim=-1, keepdim=True) # (batch_size, 1)
|
||
|
||
if idx_next == eos_id: # Stop generating early if end-of-sequence token is encountered and eos_id is specified
|
||
break
|
||
|
||
# Same as before: append sampled index to the running sequence
|
||
idx = torch.cat((idx, idx_next), dim=1) # (batch_size, num_tokens+1)
|
||
|
||
return idx
|
||
```
|
||
> [!TIP]
|
||
> `top-k` için yaygın bir alternatif olan [**`top-p`**](https://en.wikipedia.org/wiki/Top-p_sampling), aynı zamanda çekirdek örnekleme olarak da bilinir, en yüksek olasılığa sahip k örneği almak yerine, tüm sonuçlanan **kelime dağarcığını** olasılıklara göre **düzenler** ve en yüksek olasılıktan en düşük olasılığa kadar **toplar** ve bir **eşik değere ulaşana kadar** devam eder.
|
||
>
|
||
> Ardından, **yalnızca bu kelimeler** kelime dağarcığının göreli olasılıklarına göre dikkate alınacaktır.
|
||
>
|
||
> Bu, her durumda optimal k'nın farklı olabileceği için `k` örneği seçmeye gerek kalmadan, **yalnızca bir eşik** belirlemeyi sağlar.
|
||
>
|
||
> _Bu iyileştirmenin önceki kodda yer almadığını unutmayın._
|
||
|
||
> [!TIP]
|
||
> Üretilen metni iyileştirmenin bir diğer yolu, bu örnekte kullanılan açgözlü arama yerine **Beam search** kullanmaktır.\
|
||
> Açgözlü aramanın her adımda en olası bir sonraki kelimeyi seçip tek bir diziyi oluşturmasının aksine, **beam search her adımda en yüksek puan alan 𝑘 k kısmi dizileri** ( "beams" olarak adlandırılır) takip eder. Birden fazla olasılığı aynı anda keşfederek, verimlilik ve kaliteyi dengeler, açgözlü yaklaşımın erken, alt optimal seçimler nedeniyle kaçırabileceği **daha iyi bir genel** diziyi bulma şansını artırır.
|
||
>
|
||
> _Bu iyileştirmenin önceki kodda yer almadığını unutmayın._
|
||
|
||
### Loss functions
|
||
|
||
**`calc_loss_batch`** fonksiyonu, tek bir partinin tahmininin çapraz entropisini hesaplar.\
|
||
**`calc_loss_loader`** tüm partilerin çapraz entropisini alır ve **ortalama çapraz entropiyi** hesaplar.
|
||
```python
|
||
# Define loss functions
|
||
def calc_loss_batch(input_batch, target_batch, model, device):
|
||
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
|
||
logits = model(input_batch)
|
||
loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())
|
||
return loss
|
||
|
||
def calc_loss_loader(data_loader, model, device, num_batches=None):
|
||
total_loss = 0.
|
||
if len(data_loader) == 0:
|
||
return float("nan")
|
||
elif num_batches is None:
|
||
num_batches = len(data_loader)
|
||
else:
|
||
# Reduce the number of batches to match the total number of batches in the data loader
|
||
# if num_batches exceeds the number of batches in the data loader
|
||
num_batches = min(num_batches, len(data_loader))
|
||
for i, (input_batch, target_batch) in enumerate(data_loader):
|
||
if i < num_batches:
|
||
loss = calc_loss_batch(input_batch, target_batch, model, device)
|
||
total_loss += loss.item()
|
||
else:
|
||
break
|
||
return total_loss / num_batches
|
||
```
|
||
> [!TIP]
|
||
> **Gradient clipping**, büyük sinir ağlarında **eğitim istikrarını** artırmak için kullanılan bir tekniktir; bu teknik, gradyan büyüklükleri için bir **maksimum eşik** belirleyerek çalışır. Gradyanlar bu önceden tanımlanmış `max_norm` değerini aştığında, modelin parametrelerine yapılan güncellemelerin yönetilebilir bir aralıkta kalmasını sağlamak için orantılı olarak ölçeklendirilir. Bu, patlayan gradyanlar gibi sorunları önler ve daha kontrollü ve istikrarlı bir eğitim sağlar.
|
||
>
|
||
> _Bu iyileştirmenin önceki kodda yer almadığını unutmayın._
|
||
>
|
||
> Aşağıdaki örneğe bakın:
|
||
|
||
<figure><img src="../../images/image (6) (1).png" alt=""><figcaption></figcaption></figure>
|
||
|
||
### Verileri Yükleme
|
||
|
||
`create_dataloader_v1` ve `create_dataloader_v1` fonksiyonları daha önceki bir bölümde tartışılmıştır.
|
||
|
||
Buradan itibaren, metnin %90'ının eğitim için kullanılacağı, %10'unun ise doğrulama için kullanılacağı ve her iki kümenin de 2 farklı veri yükleyicisinde saklandığı belirtilmiştir.\
|
||
Bazen veri setinin bir kısmının modelin performansını daha iyi değerlendirmek için bir test seti olarak bırakıldığını unutmayın.
|
||
|
||
Her iki veri yükleyici de aynı batch boyutunu, maksimum uzunluğu, stride'ı ve işçi sayısını (bu durumda 0) kullanmaktadır.\
|
||
Ana farklar, her birinin kullandığı veridir ve doğrulayıcı, son veriyi atmamaktadır ve veriyi karıştırmamaktadır çünkü doğrulama amaçları için gerekli değildir.
|
||
|
||
Ayrıca, **stride'ın bağlam uzunluğu kadar büyük olması**, verilerin eğitiminde kullanılan bağlamlar arasında örtüşme olmayacağı anlamına gelir (aşırı uyumu azaltır ama aynı zamanda eğitim veri setini de azaltır).
|
||
|
||
Dahası, bu durumda batch boyutunun 2 olduğunu ve verilerin 2 batch'e bölündüğünü unutmayın; bunun ana amacı paralel işlemeyi sağlamak ve her batch başına tüketimi azaltmaktır.
|
||
```python
|
||
train_ratio = 0.90
|
||
split_idx = int(train_ratio * len(text_data))
|
||
train_data = text_data[:split_idx]
|
||
val_data = text_data[split_idx:]
|
||
|
||
torch.manual_seed(123)
|
||
|
||
train_loader = create_dataloader_v1(
|
||
train_data,
|
||
batch_size=2,
|
||
max_length=GPT_CONFIG_124M["context_length"],
|
||
stride=GPT_CONFIG_124M["context_length"],
|
||
drop_last=True,
|
||
shuffle=True,
|
||
num_workers=0
|
||
)
|
||
|
||
val_loader = create_dataloader_v1(
|
||
val_data,
|
||
batch_size=2,
|
||
max_length=GPT_CONFIG_124M["context_length"],
|
||
stride=GPT_CONFIG_124M["context_length"],
|
||
drop_last=False,
|
||
shuffle=False,
|
||
num_workers=0
|
||
)
|
||
```
|
||
## Sanity Checks
|
||
|
||
Amaç, eğitim için yeterli token olup olmadığını kontrol etmek, şekillerin beklenenler olup olmadığını doğrulamak ve eğitim ve doğrulama için kullanılan token sayısı hakkında bilgi almaktır:
|
||
```python
|
||
# Sanity checks
|
||
if total_tokens * (train_ratio) < GPT_CONFIG_124M["context_length"]:
|
||
print("Not enough tokens for the training loader. "
|
||
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
|
||
"increase the `training_ratio`")
|
||
|
||
if total_tokens * (1-train_ratio) < GPT_CONFIG_124M["context_length"]:
|
||
print("Not enough tokens for the validation loader. "
|
||
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
|
||
"decrease the `training_ratio`")
|
||
|
||
print("Train loader:")
|
||
for x, y in train_loader:
|
||
print(x.shape, y.shape)
|
||
|
||
print("\nValidation loader:")
|
||
for x, y in val_loader:
|
||
print(x.shape, y.shape)
|
||
|
||
train_tokens = 0
|
||
for input_batch, target_batch in train_loader:
|
||
train_tokens += input_batch.numel()
|
||
|
||
val_tokens = 0
|
||
for input_batch, target_batch in val_loader:
|
||
val_tokens += input_batch.numel()
|
||
|
||
print("Training tokens:", train_tokens)
|
||
print("Validation tokens:", val_tokens)
|
||
print("All tokens:", train_tokens + val_tokens)
|
||
```
|
||
### Eğitim ve ön hesaplamalar için cihaz seçimi
|
||
|
||
Aşağıdaki kod, kullanılacak cihazı seçer ve henüz hiçbir şey eğitilmeden bir başlangıç noktası olarak bir eğitim kaybı ve doğrulama kaybı hesaplar.
|
||
```python
|
||
# Indicate the device to use
|
||
|
||
if torch.cuda.is_available():
|
||
device = torch.device("cuda")
|
||
elif torch.backends.mps.is_available():
|
||
device = torch.device("mps")
|
||
else:
|
||
device = torch.device("cpu")
|
||
|
||
print(f"Using {device} device.")
|
||
|
||
model.to(device) # no assignment model = model.to(device) necessary for nn.Module classes
|
||
|
||
# Pre-calculate losses without starting yet
|
||
torch.manual_seed(123) # For reproducibility due to the shuffling in the data loader
|
||
|
||
with torch.no_grad(): # Disable gradient tracking for efficiency because we are not training, yet
|
||
train_loss = calc_loss_loader(train_loader, model, device)
|
||
val_loss = calc_loss_loader(val_loader, model, device)
|
||
|
||
print("Training loss:", train_loss)
|
||
print("Validation loss:", val_loss)
|
||
```
|
||
### Eğitim fonksiyonları
|
||
|
||
`generate_and_print_sample` fonksiyonu, bir bağlam alacak ve modelin o noktada ne kadar iyi olduğunu anlamak için bazı token'lar üretecektir. Bu, her adımda `train_model_simple` tarafından çağrılır.
|
||
|
||
`evaluate_model` fonksiyonu, eğitim fonksiyonuna belirtilen sıklıkta çağrılır ve model eğitimindeki o noktada eğitim kaybını ve doğrulama kaybını ölçmek için kullanılır.
|
||
|
||
Büyük fonksiyon `train_model_simple`, modeli gerçekten eğiten fonksiyondur. Beklentileri şunlardır:
|
||
|
||
- Eğitim verisi yükleyici (verilerin zaten ayrılmış ve eğitim için hazırlanmış haliyle)
|
||
- Doğrulayıcı yükleyici
|
||
- Eğitim sırasında kullanılacak **optimizer**: Bu, gradyanları kullanacak ve kaybı azaltmak için parametreleri güncelleyecek olan fonksiyondur. Bu durumda, göreceğiniz gibi, `AdamW` kullanılır, ancak daha birçok seçenek vardır.
|
||
- Her turda gradyanları biriktirmemek için `optimizer.zero_grad()` çağrılır.
|
||
- **`lr`** parametresi, modelin parametrelerini güncellerken optimizasyon sürecinde atılan **adımların boyutunu** belirleyen **öğrenme oranıdır**. Daha **küçük** bir öğrenme oranı, optimizer'ın ağırlıklara **daha küçük güncellemeler** yapması anlamına gelir, bu da daha **kesin** bir yakınsama sağlayabilir ancak eğitimi **yavaşlatabilir**. Daha **büyük** bir öğrenme oranı eğitimi hızlandırabilir ancak kayıp fonksiyonunun minimumunu **aşma riski** taşır (**kayıp fonksiyonunun minimize edildiği noktayı atlama**).
|
||
- **Ağırlık Çürümesi**, büyük ağırlıkları cezalandıran ekstra bir terim ekleyerek **Kayıp Hesaplama** adımını değiştirir. Bu, optimizer'ı daha küçük ağırlıklarla çözümler bulmaya teşvik eder, veriyi iyi bir şekilde uyum sağlamak ile modeli basit tutmak arasında denge kurarak makine öğrenimi modellerinde aşırı uyumu önler.
|
||
- L2 düzenlemesi ile SGD gibi geleneksel optimizatörler, ağırlık çürümesini kayıp fonksiyonunun gradyanı ile birleştirir. Ancak, **AdamW** (Adam optimizatörünün bir varyantı) ağırlık çürümesini gradyan güncellemesinden ayırarak daha etkili bir düzenleme sağlar.
|
||
- Eğitim için kullanılacak cihaz
|
||
- Epoch sayısı: Eğitim verisi üzerinde geçilecek süre sayısı
|
||
- Değerlendirme sıklığı: `evaluate_model` çağrılma sıklığı
|
||
- Değerlendirme iterasyonu: `generate_and_print_sample` çağrıldığında modelin mevcut durumunu değerlendirirken kullanılacak batch sayısı
|
||
- Başlangıç bağlamı: `generate_and_print_sample` çağrıldığında kullanılacak başlangıç cümlesi
|
||
- Tokenizer
|
||
```python
|
||
# Functions to train the data
|
||
def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,
|
||
eval_freq, eval_iter, start_context, tokenizer):
|
||
# Initialize lists to track losses and tokens seen
|
||
train_losses, val_losses, track_tokens_seen = [], [], []
|
||
tokens_seen, global_step = 0, -1
|
||
|
||
# Main training loop
|
||
for epoch in range(num_epochs):
|
||
model.train() # Set model to training mode
|
||
|
||
for input_batch, target_batch in train_loader:
|
||
optimizer.zero_grad() # Reset loss gradients from previous batch iteration
|
||
loss = calc_loss_batch(input_batch, target_batch, model, device)
|
||
loss.backward() # Calculate loss gradients
|
||
optimizer.step() # Update model weights using loss gradients
|
||
tokens_seen += input_batch.numel()
|
||
global_step += 1
|
||
|
||
# Optional evaluation step
|
||
if global_step % eval_freq == 0:
|
||
train_loss, val_loss = evaluate_model(
|
||
model, train_loader, val_loader, device, eval_iter)
|
||
train_losses.append(train_loss)
|
||
val_losses.append(val_loss)
|
||
track_tokens_seen.append(tokens_seen)
|
||
print(f"Ep {epoch+1} (Step {global_step:06d}): "
|
||
f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")
|
||
|
||
# Print a sample text after each epoch
|
||
generate_and_print_sample(
|
||
model, tokenizer, device, start_context
|
||
)
|
||
|
||
return train_losses, val_losses, track_tokens_seen
|
||
|
||
|
||
def evaluate_model(model, train_loader, val_loader, device, eval_iter):
|
||
model.eval() # Set in eval mode to avoid dropout
|
||
with torch.no_grad():
|
||
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
|
||
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
|
||
model.train() # Back to training model applying all the configurations
|
||
return train_loss, val_loss
|
||
|
||
|
||
def generate_and_print_sample(model, tokenizer, device, start_context):
|
||
model.eval() # Set in eval mode to avoid dropout
|
||
context_size = model.pos_emb.weight.shape[0]
|
||
encoded = text_to_token_ids(start_context, tokenizer).to(device)
|
||
with torch.no_grad():
|
||
token_ids = generate_text(
|
||
model=model, idx=encoded,
|
||
max_new_tokens=50, context_size=context_size
|
||
)
|
||
decoded_text = token_ids_to_text(token_ids, tokenizer)
|
||
print(decoded_text.replace("\n", " ")) # Compact print format
|
||
model.train() # Back to training model applying all the configurations
|
||
```
|
||
> [!TIP]
|
||
> Öğrenme oranını artırmak için **lineer ısınma** ve **kosinüs azalması** adı verilen birkaç ilgili teknik vardır.
|
||
>
|
||
> **Lineer ısınma**, başlangıç öğrenme oranını ve maksimum öğrenme oranını tanımlamak ve her epoch'tan sonra bunu sürekli güncellemektir. Bunun nedeni, eğitime daha küçük ağırlık güncellemeleriyle başlamak, modelin eğitim aşamasında büyük, dengesiz güncellemelerle karşılaşma riskini azaltmasıdır.\
|
||
> **Kosinüs azalması**, **ısınma** aşamasından sonra yarım-kosinüs eğrisi izleyerek **öğrenme oranını kademeli olarak azaltan** bir tekniktir; bu, ağırlık güncellemelerini yavaşlatarak **kaybın minimumunu aşma riskini en aza indirmeyi** ve sonraki aşamalarda eğitim istikrarını sağlamayı amaçlar.
|
||
>
|
||
> _Bu iyileştirmelerin önceki kodda yer almadığını unutmayın._
|
||
|
||
### Eğitime başla
|
||
```python
|
||
import time
|
||
start_time = time.time()
|
||
|
||
torch.manual_seed(123)
|
||
model = GPTModel(GPT_CONFIG_124M)
|
||
model.to(device)
|
||
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1)
|
||
|
||
num_epochs = 10
|
||
train_losses, val_losses, tokens_seen = train_model_simple(
|
||
model, train_loader, val_loader, optimizer, device,
|
||
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
|
||
start_context="Every effort moves you", tokenizer=tokenizer
|
||
)
|
||
|
||
end_time = time.time()
|
||
execution_time_minutes = (end_time - start_time) / 60
|
||
print(f"Training completed in {execution_time_minutes:.2f} minutes.")
|
||
```
|
||
### Eğitim evrimini yazdırma
|
||
|
||
Aşağıdaki fonksiyon ile modelin eğitim sürecindeki evrimi yazdırmak mümkündür.
|
||
```python
|
||
import matplotlib.pyplot as plt
|
||
from matplotlib.ticker import MaxNLocator
|
||
import math
|
||
def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):
|
||
fig, ax1 = plt.subplots(figsize=(5, 3))
|
||
ax1.plot(epochs_seen, train_losses, label="Training loss")
|
||
ax1.plot(
|
||
epochs_seen, val_losses, linestyle="-.", label="Validation loss"
|
||
)
|
||
ax1.set_xlabel("Epochs")
|
||
ax1.set_ylabel("Loss")
|
||
ax1.legend(loc="upper right")
|
||
ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
|
||
ax2 = ax1.twiny()
|
||
ax2.plot(tokens_seen, train_losses, alpha=0)
|
||
ax2.set_xlabel("Tokens seen")
|
||
fig.tight_layout()
|
||
plt.show()
|
||
|
||
# Compute perplexity from the loss values
|
||
train_ppls = [math.exp(loss) for loss in train_losses]
|
||
val_ppls = [math.exp(loss) for loss in val_losses]
|
||
# Plot perplexity over tokens seen
|
||
plt.figure()
|
||
plt.plot(tokens_seen, train_ppls, label='Training Perplexity')
|
||
plt.plot(tokens_seen, val_ppls, label='Validation Perplexity')
|
||
plt.xlabel('Tokens Seen')
|
||
plt.ylabel('Perplexity')
|
||
plt.title('Perplexity over Training')
|
||
plt.legend()
|
||
plt.show()
|
||
|
||
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
|
||
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
|
||
```
|
||
### Modeli Kaydet
|
||
|
||
Eğer daha sonra eğitime devam etmek istiyorsanız, modeli + optimizasyonu kaydetmek mümkündür:
|
||
```python
|
||
# Save the model and the optimizer for later training
|
||
torch.save({
|
||
"model_state_dict": model.state_dict(),
|
||
"optimizer_state_dict": optimizer.state_dict(),
|
||
},
|
||
"/tmp/model_and_optimizer.pth"
|
||
)
|
||
# Note that this model with the optimizer occupied close to 2GB
|
||
|
||
# Restore model and optimizer for training
|
||
checkpoint = torch.load("/tmp/model_and_optimizer.pth", map_location=device)
|
||
|
||
model = GPTModel(GPT_CONFIG_124M)
|
||
model.load_state_dict(checkpoint["model_state_dict"])
|
||
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-4, weight_decay=0.1)
|
||
optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
|
||
model.train(); # Put in training mode
|
||
```
|
||
Ya da sadece kullanmayı planlıyorsanız modeli:
|
||
```python
|
||
# Save the model
|
||
torch.save(model.state_dict(), "model.pth")
|
||
|
||
# Load it
|
||
model = GPTModel(GPT_CONFIG_124M)
|
||
|
||
model.load_state_dict(torch.load("model.pth", map_location=device))
|
||
|
||
model.eval() # Put in eval mode
|
||
```
|
||
## GPT2 Ağırlıklarının Yüklenmesi
|
||
|
||
GPT2 ağırlıklarını yerel olarak yüklemek için 2 hızlı betik bulunmaktadır. Her ikisi için de [https://github.com/rasbt/LLMs-from-scratch](https://github.com/rasbt/LLMs-from-scratch) deposunu yerel olarak klonlayabilirsiniz, ardından:
|
||
|
||
- Betik [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/gpt_generate.py](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/gpt_generate.py) tüm ağırlıkları indirecek ve OpenAI'den beklenen formatlara dönüştürecektir. Betik ayrıca gerekli yapılandırma ile ve "Her çaba seni ileri taşır" ifadesiyle hazırlanmıştır.
|
||
- Betik [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/02_alternative_weight_loading/weight-loading-hf-transformers.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/02_alternative_weight_loading/weight-loading-hf-transformers.ipynb) yerel olarak herhangi bir GPT2 ağırlığını yüklemenizi sağlar (sadece `CHOOSE_MODEL` değişkenini değiştirin) ve bazı istemlerden metin tahmin etmenizi sağlar.
|
||
|
||
## Referanslar
|
||
|
||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|