Bu beşinci aşamanın amacı çok basit: Tam LLM mimarisini geliştirmek. Her şeyi bir araya getirin, tüm katmanları uygulayın ve metin oluşturmak veya metni ID'lere ve geriye dönüştürmek için tüm fonksiyonları oluşturun.
Bu mimari, hem eğitim hem de eğitimden sonra metin tahmini için kullanılacaktır.
Yüksek seviyeli bir temsil aşağıda gözlemlenebilir:
Girdi (Tokenize Edilmiş Metin): Süreç, sayısal temsillere dönüştürülen tokenize edilmiş metinle başlar.
Token Gömme ve Pozisyon Gömme Katmanı: Tokenize edilmiş metin, bir token gömme katmanı ve bir pozisyon gömme katmanı aracılığıyla geçirilir; bu, kelime sırasını anlamak için kritik olan bir dizideki token'ların konumunu yakalar.
Transformer Blokları: Model, her biri birden fazla katmana sahip 12 transformer bloğu içerir. Bu bloklar aşağıdaki diziyi tekrarlar:
Masked Multi-Head Attention: Modelin girdi metninin farklı kısımlarına aynı anda odaklanmasını sağlar.
Katman Normalizasyonu: Eğitimi stabilize etmek ve geliştirmek için bir normalizasyon adımı.
Feed Forward Katmanı: Dikkat katmanından gelen bilgileri işlemek ve bir sonraki token hakkında tahminler yapmakla sorumludur.
Dropout Katmanları: Bu katmanlar, eğitim sırasında birimlerin rastgele düşürülmesiyle aşırı uyumu önler.
Son Çıktı Katmanı: Model, 50,257 boyutlu 4x50,257 boyutunda bir tensör çıktısı verir; burada 50,257 kelime dağarcığının boyutunu temsil eder. Bu tensördeki her bir satır, modelin dizideki bir sonraki kelimeyi tahmin etmek için kullandığı bir vektöre karşılık gelir.
Amaç: Amaç, bu gömmeleri alıp tekrar metne dönüştürmektir. Özellikle, çıktının son satırı, bu diyagramda "ileri" olarak temsil edilen bir sonraki kelimeyi oluşturmak için kullanılır.
Kod temsili
import torchimport torch.nn as nnimport tiktokenclassGELU(nn.Module):def__init__(self):super().__init__()defforward(self,x):return0.5* x * (1+ torch.tanh(torch.sqrt(torch.tensor(2.0/ torch.pi)) *(x +0.044715* torch.pow(x, 3))))classFeedForward(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"]),)defforward(self,x):return self.layers(x)classMultiHeadAttention(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 num_heads"self.d_out = d_outself.num_heads = num_headsself.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dimself.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 outputsself.dropout = nn.Dropout(dropout)self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1))defforward(self,x):b, num_tokens, d_in = x.shapekeys = 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 maskattn_scores = queries @ keys.transpose(2, 3)# Dot product for each head# Original mask truncated to the number of tokens and converted to booleanmask_bool = self.mask.bool()[:num_tokens,:num_tokens]# Use the mask to fill attention scoresattn_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_dimcontext_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)context_vec = self.out_proj(context_vec)# optional projectionreturn context_vecclassLayerNorm(nn.Module):def__init__(self,emb_dim):super().__init__()self.eps =1e-5self.scale = nn.Parameter(torch.ones(emb_dim))self.shift = nn.Parameter(torch.zeros(emb_dim))defforward(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.shiftclassTransformerBlock(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"])defforward(self,x):# Shortcut connection for attention blockshortcut = xx = 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 blockshortcut = xx = self.norm2(x)x = self.ff(x)x = self.drop_shortcut(x)x = x + shortcut # Add the original input backreturn xclassGPTModel(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 _ inrange(cfg["n_layers"])])self.final_norm =LayerNorm(cfg["emb_dim"])self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)defforward(self,in_idx):batch_size, seq_len = in_idx.shapetok_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 logitsGPT_CONFIG_124M ={"vocab_size":50257,# Vocabulary size"context_length":1024,# Context length"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)out =model(batch)print("Input batch:\n", batch)print("\nOutput shape:", out.shape)print(out)
GELU Aktivasyon Fonksiyonu
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04classGELU(nn.Module):def__init__(self):super().__init__()defforward(self,x):return0.5* x * (1+ torch.tanh(torch.sqrt(torch.tensor(2.0/ torch.pi)) *(x +0.044715* torch.pow(x, 3))))
Amaç ve İşlevsellik
GELU (Gaussian Error Linear Unit): Modele doğrusal olmayanlık katan bir aktivasyon fonksiyonu.
Düzgün Aktivasyon: Negatif girdileri sıfıra indiren ReLU'nun aksine, GELU negatif girdiler için küçük, sıfırdan farklı değerler alarak girdileri çıktılara düzgün bir şekilde eşler.
Matematiksel Tanım:
Bu fonksiyonun FeedForward katmanındaki doğrusal katmanlardan sonra kullanılmasının amacı, doğrusal verileri doğrusal olmayan hale getirerek modelin karmaşık, doğrusal olmayan ilişkileri öğrenmesine olanak tanımaktır.
FeedForward Sinir Ağı
Matrislerin şekillerini daha iyi anlamak için yorum olarak şekiller eklenmiştir:
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04classFeedForward(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"]),)defforward(self,x):# x shape: (batch_size, seq_len, emb_dim)x = self.layers[0](x)# x shape: (batch_size, seq_len, 4 * emb_dim)x = self.layers[1](x)# x shape remains: (batch_size, seq_len, 4 * emb_dim)x = self.layers[2](x)# x shape: (batch_size, seq_len, emb_dim)return x # Output shape: (batch_size, seq_len, emb_dim)
Amaç ve İşlevsellik
Pozisyon Bazında FeedForward Ağı: Her pozisyona ayrı ve benzer şekilde iki katmanlı tam bağlantılı bir ağ uygular.
Katman Detayları:
İlk Lineer Katman: Boyutları emb_dim'den 4 * emb_dim'ye genişletir.
GELU Aktivasyonu: Doğrusal olmayanlık uygular.
İkinci Lineer Katman: Boyutları tekrar emb_dim'ye düşürür.
Gördüğünüz gibi, Feed Forward ağı 3 katman kullanır. İlk katman, boyutları 4 ile çarpacak lineer bir katmandır (model içinde eğitilecek parametreler). Ardından, daha zengin temsilleri yakalamak için tüm bu boyutlarda doğrusal olmayan varyasyonlar uygulamak üzere GELU fonksiyonu kullanılır ve nihayetinde orijinal boyutlara geri dönmek için başka bir lineer katman kullanılır.
Çoklu Başlı Dikkat Mekanizması
Bu daha önceki bir bölümde açıklandı.
Amaç ve İşlevsellik
Çoklu Başlı Kendine Dikkat: Modelin bir token'ı kodlarken girdi dizisi içindeki farklı pozisyonlara odaklanmasını sağlar.
Ana Bileşenler:
Sorgular, Anahtarlar, Değerler: Dikkat puanlarını hesaplamak için kullanılan girdi lineer projeksiyonları.
Başlar: Paralel çalışan birden fazla dikkat mekanizması (num_heads), her biri azaltılmış bir boyutla (head_dim).
Dikkat Puanları: Sorgular ve anahtarların nokta çarpımı olarak hesaplanır, ölçeklendirilir ve maske uygulanır.
Maskeleme: Gelecek token'lara dikkat edilmesini önlemek için nedensel bir maske uygulanır (GPT gibi otoregresif modeller için önemlidir).
Dikkat Ağırlıkları: Maskelenmiş ve ölçeklendirilmiş dikkat puanlarının softmax'ı.
Bağlam Vektörü: Dikkat ağırlıklarına göre değerlerin ağırlıklı toplamı.
Çıktı Projeksiyonu: Tüm başların çıktısını birleştirmek için lineer katman.
Bu ağın amacı, aynı bağlamdaki token'lar arasındaki ilişkileri bulmaktır. Ayrıca, son ilişkilerin her başta bulunmasını sağlamak için token'lar farklı başlara bölünür, böylece aşırı uyum önlenir.
Ayrıca, eğitim sırasında belirli bir token'a ilişkin ilişkileri incelerken daha sonraki token'ların dikkate alınmaması için bir nedensel maske uygulanır ve aşırı uyumu önlemek için bazı dropout uygulanır.
Katman Normalizasyon
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04classLayerNorm(nn.Module):def__init__(self,emb_dim):super().__init__()self.eps =1e-5# Prevent division by zero during normalization.self.scale = nn.Parameter(torch.ones(emb_dim))self.shift = nn.Parameter(torch.zeros(emb_dim))defforward(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
Amaç ve İşlevsellik
Katman Normalizasyonu: Bir partideki her bireysel örnek için özellikler (gömme boyutları) boyunca girişleri normalleştirmek için kullanılan bir teknik.
Bileşenler:
eps: Normalizasyon sırasında sıfıra bölmeyi önlemek için varyansa eklenen küçük bir sabit (1e-5).
scale ve shift: Normalleştirilmiş çıktıyı ölçeklendirmek ve kaydırmak için modelin öğrenebileceği parametreler (nn.Parameter). Sırasıyla birler ve sıfırlar ile başlatılır.
Normalizasyon Süreci:
Ortalama Hesaplama (mean): Gömme boyutu boyunca giriş x'in ortalamasını hesaplar (dim=-1), yayılma için boyutu korur (keepdim=True).
Varyans Hesaplama (var): Gömme boyutu boyunca x'in varyansını hesaplar, boyutu da korur. unbiased=False parametresi, varyansın yanlı tahminci kullanılarak hesaplanmasını sağlar (örnek sayısı N yerine N-1 ile bölünerek), bu da örnekler yerine özellikler üzerinde normalleştirme yaparken uygundur.
Normalleştirme (norm_x):x'ten ortalamayı çıkarır ve varyansın karekökü artı eps ile böler.
Ölçekleme ve Kaydırma: Normalleştirilmiş çıktıya öğrenilebilir scale ve shift parametrelerini uygular.
Amaç, aynı token'ın tüm boyutları boyunca 0 ortalama ve 1 varyans sağlamaktır. Bunun amacı, derin sinir ağlarının eğitimini stabilize etmek için iç değişken kaymasını azaltmaktır; bu, eğitim sırasında parametrelerin güncellenmesi nedeniyle ağ aktivasyonlarının dağılımındaki değişimi ifade eder.
Dönüştürücü Bloğu
Şekillerin matrislerin şekillerini daha iyi anlamak için yorum olarak eklendi:
Artımlı Ekle (x + shortcut): İlk artımlı yoldan gelen girişle birleştir.
Transformer bloğu tüm ağları bir araya toplar ve eğitim kararlılığını ve sonuçlarını iyileştirmek için bazı normalizasyon ve dropout uygular.
Dropout'ların her ağın kullanımından sonra yapıldığını, normalizasyonun ise öncesinde uygulandığını not edin.
Ayrıca, bir ağın çıkışını girişiyle eklemeyi içeren kısa yolları da kullanır. Bu, başlangıç katmanlarının son katmanlar kadar "katkıda bulunmasını" sağlayarak kaybolan gradyan sorununu önlemeye yardımcı olur.
GPTModel
Şekillerin daha iyi anlaşılması için yorum olarak eklenmiştir:
Token Gömme (tok_emb): Token indekslerini gömülere dönüştürür. Hatırlatma olarak, bunlar kelime dağarcığındaki her tokenin her boyutuna verilen ağırlıklardır.
Pozisyon Gömme (pos_emb): Gömülere pozisyon bilgisi ekleyerek tokenlerin sırasını yakalar. Hatırlatma olarak, bunlar metindeki pozisyonuna göre tokenlere verilen ağırlıklardır.
Dropout (drop_emb): Gömülere düzenleme için uygulanır.
Transformer Blokları (trf_blocks): Gömüleri işlemek için n_layers transformer bloğunun yığını.
Son Normalizasyon (final_norm): Çıktı katmanından önce katman normalizasyonu.
Çıktı Katmanı (out_head): Son gizli durumları kelime dağarcığı boyutuna projekte ederek tahmin için logitleri üretir.
Bu sınıfın amacı, bir dizideki bir sonraki tokeni tahmin etmek için diğer bahsedilen tüm ağları kullanmaktır; bu, metin üretimi gibi görevler için temeldir.
Ne kadar belirtilen kadar transformer bloğu kullanacağını ve her transformer bloğunun bir çoklu başlık dikkat ağı, bir ileri besleme ağı ve birkaç normalizasyon kullandığını not edin. Yani 12 transformer bloğu kullanılıyorsa, bunu 12 ile çarpın.
Ayrıca, çıktıdan önce bir normalizasyon katmanı eklenir ve sonuçları uygun boyutlarla almak için sonunda bir son lineer katman uygulanır. Her son vektörün kullanılan kelime dağarcığı boyutuna sahip olduğunu not edin. Bu, kelime dağarcığındaki her olası token için bir olasılık elde etmeye çalıştığı içindir.
Eğitilecek Parametre Sayısı
GPT yapısı tanımlandığında, eğitilecek parametre sayısını bulmak mümkündür:
GPT_CONFIG_124M ={"vocab_size":50257,# Vocabulary size"context_length":1024,# Context length"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}model =GPTModel(GPT_CONFIG_124M)total_params =sum(p.numel() for p in model.parameters())print(f"Total number of parameters: {total_params:,}")# Total number of parameters: 163,009,536
Önceki gibi bir sonraki token'ı tahmin eden bir modele sahip olduğunuzda, çıktının son token değerlerini almak yeterlidir (çünkü bunlar tahmin edilen token'ın değerleri olacaktır), bu da sözlükteki her bir giriş için bir değer olacak ve ardından softmax fonksiyonunu kullanarak boyutları 1'e toplam olan olasılıklara normalize etmek ve ardından en büyük girişin indeksini almak, bu da sözlükteki kelimenin indeksi olacaktır.
defgenerate_text_simple(model,idx,max_new_tokens,context_size):# idx is (batch, n_tokens) array of indices in the current contextfor _ inrange(max_new_tokens):# Crop current context if it exceeds the supported context size# E.g., if LLM supports only 5 tokens, and the context size is 10# then only the last 5 tokens are used as contextidx_cond = idx[:,-context_size:]# Get the predictionswith torch.no_grad():logits =model(idx_cond)# Focus only on the last time step# (batch, n_tokens, vocab_size) becomes (batch, vocab_size)logits = logits[:,-1,:]# Apply softmax to get probabilitiesprobas = torch.softmax(logits, dim=-1)# (batch, vocab_size)# Get the idx of the vocab entry with the highest probability valueidx_next = torch.argmax(probas, dim=-1, keepdim=True)# (batch, 1)# Append sampled index to the running sequenceidx = torch.cat((idx, idx_next), dim=1)# (batch, n_tokens+1)return idxstart_context ="Hello, I am"encoded = tokenizer.encode(start_context)print("encoded:", encoded)encoded_tensor = torch.tensor(encoded).unsqueeze(0)print("encoded_tensor.shape:", encoded_tensor.shape)model.eval()# disable dropoutout =generate_text_simple(model=model,idx=encoded_tensor,max_new_tokens=6,context_size=GPT_CONFIG_124M["context_length"])print("Output:", out)print("Output length:", len(out[0]))