8.5. Yinelemeli Sinir Ağlarının Sıfırdan Uygulanması¶ Open the notebook in SageMaker Studio Lab
Bu bölümde, Section 8.4 içindeki tanımlamalarımıza göre, karakter düzeyinde bir dil modeli için sıfırdan bir RNN uygulayacağız. Bu tarz bir model H. G. Wells’in Zaman Makinesi ile eğitilecek. Daha önce olduğu gibi, önce Section 8.3 içinde tanıtılan veri kümesini okuyarak başlıyoruz.
%matplotlib inline
import math
from d2l import mxnet as d2l
from mxnet import autograd, gluon, np, npx
npx.set_np()
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
%matplotlib inline
import math
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
%matplotlib inline
import math
import tensorflow as tf
from d2l import tensorflow as d2l
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
train_random_iter, vocab_random_iter = d2l.load_data_time_machine(
batch_size, num_steps, use_random_iter=True)
8.5.1. Bire Bir Kodlama¶
Her andıcın train_iter
’te sayısal bir indeks olarak temsil
edildiğini hatırlayın. Bu indeksleri doğrudan sinir ağına beslemek
öğrenmeyi zorlaştırabilir. Genellikle her andıcı daha açıklayıcı bir
öznitelik vektörü olarak temsil ediyoruz. En kolay gösterim,
Section 3.4.1 içinde tanıtılan bire bir
kodlamadır (one-hot coding).
Özetle, her bir indeksi farklı bir birim vektörüne eşleriz: Kelime
dağarcığındaki farklı andıçların sayısının \(N\) (len(vocab)
)
olduğunu ve andıç indekslerinin \(0\) ile \(N-1\) arasında
değiştiğini varsayalım. Bir andıç indeksi \(i\) tamsayısı ise, o
zaman \(N\) uzunluğunda tümü 0 olan bir vektör oluştururuz ve
\(i\) konumundaki elemanı 1’e ayarlarız. Bu vektör, orijinal andıcın
bire bir kodlama vektörüdür. İndeksleri 0 ve 2 olan bire bir kodlama
vektörleri aşağıda gösterilmiştir.
npx.one_hot(np.array([0, 2]), len(vocab))
array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])
F.one_hot(torch.tensor([0, 2]), len(vocab))
tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0]])
tf.one_hot(tf.constant([0, 2]), len(vocab))
<tf.Tensor: shape=(2, 28), dtype=float32, numpy=
array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)>
Her seferinde örnek aldığımız minigrubun şeklidir (grup boyutu, zaman
adımlarının sayısı). one_hot
işlevi, böyle bir minigrubu, son
boyutun kelime dağarcığı uzunluğuna eşit olduğu üç boyutlu bir tensöre
dönüştürür (len(vocab)
). Girdiyi sıklıkla deviririz (transpose),
böylece (zaman adımlarının sayısı, grup boyutu, kelime dağarcığı
uzunluğu) şeklinde bir çıktı elde edeceğiz. Bu, bir minigrubun gizli
durumlarını zaman adımlarıyla güncellerken en dıştaki boyutta daha rahat
döngü yapmamızı sağlayacaktır.
X = np.arange(10).reshape((2, 5))
npx.one_hot(X.T, 28).shape
(5, 2, 28)
X = torch.arange(10).reshape((2, 5))
F.one_hot(X.T, 28).shape
torch.Size([5, 2, 28])
X = tf.reshape(tf.range(10), (2, 5))
tf.one_hot(tf.transpose(X), 28).shape
TensorShape([5, 2, 28])
8.5.2. Model Parametrelerini İlkleme¶
Ardından, RNN modeli için model parametrelerini ilkliyoruz. Gizli
birimlerin sayısı num_hiddens
ayarlanabilir bir hiper parametredir.
Dil modellerini eğitirken, girdiler ve çıktılar aynı kelime
dağarcığındandır. Bu nedenle, kelime dağarcığı uzunluğuna eşit olan aynı
boyuta sahiptirler.
def get_params(vocab_size, num_hiddens, device):
num_inputs = num_outputs = vocab_size
def normal(shape):
return np.random.normal(scale=0.01, size=shape, ctx=device)
# Gizli katman parametreleri
W_xh = normal((num_inputs, num_hiddens))
W_hh = normal((num_hiddens, num_hiddens))
b_h = np.zeros(num_hiddens, ctx=device)
# Çıktı katmanı parametreleri
W_hq = normal((num_hiddens, num_outputs))
b_q = np.zeros(num_outputs, ctx=device)
# Gradyanları iliştir
params = [W_xh, W_hh, b_h, W_hq, b_q]
for param in params:
param.attach_grad()
return params
def get_params(vocab_size, num_hiddens, device):
num_inputs = num_outputs = vocab_size
def normal(shape):
return torch.randn(size=shape, device=device) * 0.01
# Gizli katman parametreleri
W_xh = normal((num_inputs, num_hiddens))
W_hh = normal((num_hiddens, num_hiddens))
b_h = torch.zeros(num_hiddens, device=device)
# Çıktı katmanı parametreleri
W_hq = normal((num_hiddens, num_outputs))
b_q = torch.zeros(num_outputs, device=device)
# Gradyanları iliştir
params = [W_xh, W_hh, b_h, W_hq, b_q]
for param in params:
param.requires_grad_(True)
return params
def get_params(vocab_size, num_hiddens):
num_inputs = num_outputs = vocab_size
def normal(shape):
return tf.random.normal(shape=shape,stddev=0.01,mean=0,dtype=tf.float32)
# Gizli katman parametreleri
W_xh = tf.Variable(normal((num_inputs, num_hiddens)), dtype=tf.float32)
W_hh = tf.Variable(normal((num_hiddens, num_hiddens)), dtype=tf.float32)
b_h = tf.Variable(tf.zeros(num_hiddens), dtype=tf.float32)
# Çıktı katmanı parametreleri
W_hq = tf.Variable(normal((num_hiddens, num_outputs)), dtype=tf.float32)
b_q = tf.Variable(tf.zeros(num_outputs), dtype=tf.float32)
params = [W_xh, W_hh, b_h, W_hq, b_q]
return params
8.5.3. RNN Modeli¶
Bir RNN modeli tanımlamak için, ilk olarak ilkleme esnasında gizli
durumu döndürmek için bir init_rnn_state
işlevi gerekir. Şekli (grup
boyutu, gizli birimlerin sayısı) olan 0 ile doldurulmuş bir tensör
döndürür. Çokuzlu (tuple) kullanmak, daha sonraki bölümlerde
karşılaşacağımız gibi gizli durumun birden çok değişken içerdiği halleri
işlemeyi kolaylaştırır.
def init_rnn_state(batch_size, num_hiddens, device):
return (np.zeros((batch_size, num_hiddens), ctx=device), )
def init_rnn_state(batch_size, num_hiddens, device):
return (torch.zeros((batch_size, num_hiddens), device=device), )
def init_rnn_state(batch_size, num_hiddens):
return (tf.zeros((batch_size, num_hiddens)), )
Aşağıdaki rnn
işlevi, gizli durumu ve çıktıyı bir zaman adımında
nasıl hesaplayacağınızı tanımlar. RNN modelinin inputs
değişkeninin
en dış boyutunda döngü yaptığını, böylece minigrubun gizli durumları
H
’nin her zaman adımında güncellediğini unutmayın. Ayrıca, burada
etkinleştirme fonksiyonu olarak \(\tanh\) işlevi kullanılır.
Section 4.1 içinde açıklandığı gibi, \(\tanh\) işlevinin
ortalama değeri, elemanlar gerçel sayılar üzerinde eşit olarak
dağıtıldığında 0’dır.
def rnn(inputs, state, params):
# `inputs`'un şekli: (`num_steps`, `batch_size`, `vocab_size`)
W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
# `X`'in şekli: (`batch_size`, `vocab_size`)
for X in inputs:
H = np.tanh(np.dot(X, W_xh) + np.dot(H, W_hh) + b_h)
Y = np.dot(H, W_hq) + b_q
outputs.append(Y)
return np.concatenate(outputs, axis=0), (H,)
def rnn(inputs, state, params):
# `inputs`'un şekli: (`num_steps`, `batch_size`, `vocab_size`)
W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
# `X`'in şekli: (`batch_size`, `vocab_size`)
for X in inputs:
H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)
Y = torch.mm(H, W_hq) + b_q
outputs.append(Y)
return torch.cat(outputs, dim=0), (H,)
def rnn(inputs, state, params):
# `inputs`'un şekli: (`num_steps`, `batch_size`, `vocab_size`)
W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
# `X`'in şekli: (`batch_size`, `vocab_size`)
for X in inputs:
X = tf.reshape(X,[-1,W_xh.shape[0]])
H = tf.tanh(tf.matmul(X, W_xh) + tf.matmul(H, W_hh) + b_h)
Y = tf.matmul(H, W_hq) + b_q
outputs.append(Y)
return tf.concat(outputs, axis=0), (H,)
Gerekli tüm işlevler tanımlandıktan sonra, bu işlevleri sarmalamak ve sıfırdan uygulanan bir RNN modelinin parametreleri depolamak için bir sınıf oluşturuyoruz.
class RNNModelScratch: #@save
"""Sıfırdan uygulanan bir RNN modeli."""
def __init__(self, vocab_size, num_hiddens, device, get_params,
init_state, forward_fn):
self.vocab_size, self.num_hiddens = vocab_size, num_hiddens
self.params = get_params(vocab_size, num_hiddens, device)
self.init_state, self.forward_fn = init_state, forward_fn
def __call__(self, X, state):
X = npx.one_hot(X.T, self.vocab_size)
return self.forward_fn(X, state, self.params)
def begin_state(self, batch_size, ctx):
return self.init_state(batch_size, self.num_hiddens, ctx)
class RNNModelScratch: #@save
"""Sıfırdan uygulanan bir RNN modeli."""
def __init__(self, vocab_size, num_hiddens, device,
get_params, init_state, forward_fn):
self.vocab_size, self.num_hiddens = vocab_size, num_hiddens
self.params = get_params(vocab_size, num_hiddens, device)
self.init_state, self.forward_fn = init_state, forward_fn
def __call__(self, X, state):
X = F.one_hot(X.T, self.vocab_size).type(torch.float32)
return self.forward_fn(X, state, self.params)
def begin_state(self, batch_size, device):
return self.init_state(batch_size, self.num_hiddens, device)
class RNNModelScratch: #@save
"""Sıfırdan uygulanan bir RNN modeli."""
def __init__(self, vocab_size, num_hiddens,
init_state, forward_fn, get_params):
self.vocab_size, self.num_hiddens = vocab_size, num_hiddens
self.init_state, self.forward_fn = init_state, forward_fn
self.trainable_variables = get_params(vocab_size, num_hiddens)
def __call__(self, X, state):
X = tf.one_hot(tf.transpose(X), self.vocab_size)
X = tf.cast(X, tf.float32)
return self.forward_fn(X, state, self.trainable_variables)
def begin_state(self, batch_size, *args, **kwargs):
return self.init_state(batch_size, self.num_hiddens)
Çıktıların doğru şekillere sahip olup olmadığını kontrol edelim, örn. gizli durumun boyutunun değişmeden kalmasını sağlamak için.
num_hiddens = 512
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,
init_rnn_state, rnn)
state = net.begin_state(X.shape[0], d2l.try_gpu())
Y, new_state = net(X.as_in_context(d2l.try_gpu()), state)
Y.shape, len(new_state), new_state[0].shape
((10, 28), 1, (2, 512))
num_hiddens = 512
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,
init_rnn_state, rnn)
state = net.begin_state(X.shape[0], d2l.try_gpu())
Y, new_state = net(X.to(d2l.try_gpu()), state)
Y.shape, len(new_state), new_state[0].shape
(torch.Size([10, 28]), 1, torch.Size([2, 512]))
# defining tensorflow training strategy
device_name = d2l.try_gpu()._device_name
strategy = tf.distribute.OneDeviceStrategy(device_name)
num_hiddens = 512
with strategy.scope():
net = RNNModelScratch(len(vocab), num_hiddens, init_rnn_state, rnn,
get_params)
state = net.begin_state(X.shape[0])
Y, new_state = net(X, state)
Y.shape, len(new_state), new_state[0].shape
(TensorShape([10, 28]), 1, TensorShape([2, 512]))
Çıktı şekli (zaman sayısı \(\times\) grup boyutu, kelime dağarcığı uzunluğu) iken, gizli durum şeklinin aynı kaldığını, yani (grup boyutu, gizli birimlerin sayısı) olduğunu görebiliriz.
8.5.4. Tahmin¶
Önce kullanıcı tarafından sağlanan prefix
’i (önek), ki birkaç
karakter içeren bir dizgidir, takip eden yeni karakterler oluşturmak
için tahmin işlevi tanımlayalım. prefix
’teki bu başlangıç
karakterleri arasında döngü yaparken, herhangi bir çıktı oluşturmadan
gizli durumu bir sonraki adımına geçirmeye devam ediyoruz. Buna, modelin
kendisini güncellediği (örn. gizli durumu güncellediği) ancak
tahminlerde bulunmadığı ısınma dönemi denir. Isınma döneminden sonra,
gizli durum genellikle başlangıçtaki ilkleme değerinden daha iyidir.
Artık tahmin edilen karakterleri oluşturup yayarız.
def predict_ch8(prefix, num_preds, net, vocab, device): #@save
"""`prefix`'i takip eden yeni karakterler üret."""
state = net.begin_state(batch_size=1, ctx=device)
outputs = [vocab[prefix[0]]]
get_input = lambda: np.array([outputs[-1]], ctx=device).reshape((1, 1))
for y in prefix[1:]: # Warm-up period
_, state = net(get_input(), state)
outputs.append(vocab[y])
for _ in range(num_preds): # Predict `num_preds` steps
y, state = net(get_input(), state)
outputs.append(int(y.argmax(axis=1).reshape(1)))
return ''.join([vocab.idx_to_token[i] for i in outputs])
def predict_ch8(prefix, num_preds, net, vocab, device): #@save
"""`prefix`'i takip eden yeni karakterler üret."""
state = net.begin_state(batch_size=1, device=device)
outputs = [vocab[prefix[0]]]
get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1))
for y in prefix[1:]: # Warm-up period
_, state = net(get_input(), state)
outputs.append(vocab[y])
for _ in range(num_preds): # Predict `num_preds` steps
y, state = net(get_input(), state)
outputs.append(int(y.argmax(dim=1).reshape(1)))
return ''.join([vocab.idx_to_token[i] for i in outputs])
def predict_ch8(prefix, num_preds, net, vocab): #@save
"""`prefix`'i takip eden yeni karakterler üret."""
state = net.begin_state(batch_size=1, dtype=tf.float32)
outputs = [vocab[prefix[0]]]
get_input = lambda: tf.reshape(tf.constant([outputs[-1]]), (1, 1)).numpy()
for y in prefix[1:]: # Warm-up period
_, state = net(get_input(), state)
outputs.append(vocab[y])
for _ in range(num_preds): # Predict `num_preds` steps
y, state = net(get_input(), state)
outputs.append(int(y.numpy().argmax(axis=1).reshape(1)))
return ''.join([vocab.idx_to_token[i] for i in outputs])
Şimdi predict_ch8
işlevini test edebiliriz. Öneki time traveller
olarak belirtiyoruz ve 10 ek karakter üretiyoruz. Ağı eğitmediğimiz göz
önüne alındığında, saçma tahminler üretecektir.
predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu())
'time traveller iiiiiiiiii'
predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu())
'time traveller lfbrpfbehs'
predict_ch8('time traveller ', 10, net, vocab)
'time traveller <unk>wqlmuydh '
8.5.5. Gradyan Kırpma¶
\(T\) uzunluğunda bir dizi için, bir yinelemede bu \(T\) zaman adımlarının üzerindeki gradyanları hesaplarız, bu da geri yayma sırasında \(\mathcal{O}(T)\) uzunluğunda bir matris çarpımları zincirine neden olur. :numref:`sec_numerical_stability içinde belirtildiği gibi, bu da sayısal kararsızlığa neden olabilir, örneğin \(T\) büyük olduğunda gradyanlar patlayabilir veya kaybolabilir. Bu nedenle, RNN modelleri genellikle eğitimi kararlı tutmak için ekstra yardıma ihtiyaç duyar.
Genel olarak, bir eniyileme problemini çözerken, model parametresi için güncelleme adımları atıyoruz, mesela \(\mathbf{x}\) vektör formunda, bir minigrup üzerinden negatif gradyan \(\mathbf{g}\) yönünde. Örneğin, öğrenme oranı \(\eta > 0\) ise, bir yinelemede \(\mathbf{x}\)’i \(\mathbf{x} - \eta \mathbf{g}\) olarak güncelleriz. \(f\) amaç fonksiyonunun iyi davrandığını varsayalım, diyelim ki, \(L\) sabiti ile Lipschitz sürekli olsun. Yani, herhangi bir \(\mathbf{x}\) ve \(\mathbf{y}\) için:
Bu durumda, parametre vektörünü \(\eta \mathbf{g}\) ile güncellediğimizi varsayarsak, o zaman
demektir ki \(L \eta \|\mathbf{g}\|\)’den daha büyük bir değişiklik gözlemlemeyeceğiz. Bu hem bir lanet hem de bir nimettir. Lanet tarafında, ilerleme kaydetme hızını sınırlar; oysa nimet tarafında, yanlış yönde hareket edersek işlerin ne ölçüde ters gidebileceğini sınırlar.
Bazen gradyanlar oldukça büyük olabilir ve eniyileme algoritması yakınsamada başarısız olabilir. Öğrenme hızı \(\eta\)’yı azaltarak bunu ele alabiliriz. Ama ya sadece nadiren büyük gradyanlar alırsak? Bu durumda böyle bir yaklaşım tamamen yersiz görünebilir. Popüler bir alternatif, \(\mathbf{g}\) gradyanını belirli bir yarıçaptaki bir küreye geri yansıtmaktır, diyelim ki \(\theta\) için:
Bunu yaparak, gradyanın normunun \(\theta\)’yı asla geçmediğini ve güncellenen gradyanın \(\mathbf{g}\)’nin orijinal yönüyle tamamen hizalandığını biliyoruz. Ayrıca, herhangi bir minigrubun (ve içindeki herhangi bir örneklemin) parametre vektörü üzerinde uygulayabileceği etkiyi sınırlaması gibi arzu edilen yan etkiye sahiptir. Bu, modele belirli bir derecede gürbüzlük kazandırır. Gradyan kırpma gradyan patlaması için hızlı bir düzeltme sağlar. Sorunu tamamen çözmese de, onu hafifletmede kullanılan birçok teknikten biridir.
Aşağıda, sıfırdan uygulanan veya üst düzey API’ler tarafından oluşturulan bir modelin gradyanlarını kırpmak için bir işlev tanımlıyoruz. Ayrıca, tüm model parametrelerinin üzerinden gradyan normunu hesapladığımızı unutmayın.
def grad_clipping(net, theta): #@save
"""Gradyanı kırp."""
if isinstance(net, gluon.Block):
params = [p.data() for p in net.collect_params().values()]
else:
params = net.params
norm = math.sqrt(sum((p.grad ** 2).sum() for p in params))
if norm > theta:
for param in params:
param.grad[:] *= theta / norm
def grad_clipping(net, theta): #@save
"""Gradyanı kırp."""
if isinstance(net, nn.Module):
params = [p for p in net.parameters() if p.requires_grad]
else:
params = net.params
norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
if norm > theta:
for param in params:
param.grad[:] *= theta / norm
def grad_clipping(grads, theta): #@save
"""Gradyanı kırp."""
theta = tf.constant(theta, dtype=tf.float32)
new_grad = []
for grad in grads:
if isinstance(grad, tf.IndexedSlices):
new_grad.append(tf.convert_to_tensor(grad))
else:
new_grad.append(grad)
norm = tf.math.sqrt(sum((tf.reduce_sum(grad ** 2)).numpy()
for grad in new_grad))
norm = tf.cast(norm, tf.float32)
if tf.greater(norm, theta):
for i, grad in enumerate(new_grad):
new_grad[i] = grad * theta / norm
else:
new_grad = new_grad
return new_grad
8.5.6. Eğitim¶
Modeli eğitmeden önce, modeli bir dönemde eğitmek için bir işlev tanımlayalım. Section 3.6 içindeki model eğitimimizden üç yerde farklılık gösterir:
Dizili veriler için farklı örnekleme yöntemleri (rastgele örnekleme ve sıralı bölümleme), gizli durumların ilklenmesinde farklılıklara neden olacaktır.
Model parametrelerini güncellemeden önce gradyanları kırpıyoruz. Bu, eğitim süreci sırasında bir noktada gradyanlar patladığında bile modelin ıraksamamasını sağlar.
Modeli değerlendirmek için şaşkınlığı kullanıyoruz. Section 8.4.4 içinde tartışıldığı gibi, bu farklı uzunluktaki dizilerin karşılaştırılabilir olmasını sağlar.
Özellikle, sıralı bölümleme kullanıldığında, gizli durumu yalnızca her dönemin başında ilkleriz. Sonraki minigruptaki \(i\). altdizi örneği şimdiki \(i\). altdizi örneğine bitişik olduğundan, şimdiki minigrup sonundaki gizli durum sonraki minigrup başında gizli durumu ilklemek için kullanılır. Bu şekilde, gizli durumda saklanan dizinin tarihsel bilgileri bir dönem içinde bitişik altdizilere aktarılabilir. Ancak, herhangi bir noktada gizli durumun hesaplanması, aynı dönemdeki tüm önceki minigruplara bağlıdır, ki bu da gradyan hesaplamasını zorlaştırır. Hesaplama maliyetini azaltmak için, herhangi bir minigrup işleminden önce gradyanı koparıp ayırırız, böylece gizli durumun gradyan hesaplaması her zaman minigrup içindeki zaman adımlarıyla sınırlı kalır.
Rastgele örneklemeyi kullanırken, her örnek rastgele bir konumla
örneklendiğinden, her yineleme için gizli durumu yeniden ilklememiz
gerekir. Section 3.6 içindeki train_epoch_ch3
işlevi gibi, updater
da model parametrelerini güncellemek için genel
bir işlevdir. Sıfırdan uygulanan d2l.sgd
işlevi veya derin bir
öğrenme çerçevesindeki yerleşik eniyileme işlevi olabilir.
#@save
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):
"""Bir modeli bir dönem içinde eğitin (Bölüm 8'de tanımlanmıştır)."""
state, timer = None, d2l.Timer()
metric = d2l.Accumulator(2) # Eğitim kaybı toplamı, andıç sayısı
for X, Y in train_iter:
if state is None or use_random_iter:
# İlk yineleme olduğunda veya rastgele örnekleme kullanıldığında
# `state`'i (durumu) ilklet
state = net.begin_state(batch_size=X.shape[0], ctx=device)
else:
for s in state:
s.detach()
y = Y.T.reshape(-1)
X, y = X.as_in_ctx(device), y.as_in_ctx(device)
with autograd.record():
y_hat, state = net(X, state)
l = loss(y_hat, y).mean()
l.backward()
grad_clipping(net, 1)
updater(batch_size=1) # `mean` işlevi çağrıldığından dolayı
metric.add(l * d2l.size(y), d2l.size(y))
return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()
#@save
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):
"""Bir modeli bir dönem içinde eğitin (Bölüm 8'de tanımlanmıştır)."""
state, timer = None, d2l.Timer()
metric = d2l.Accumulator(2) # Eğitim kaybı toplamı, andıç sayısı
for X, Y in train_iter:
if state is None or use_random_iter:
# İlk yineleme olduğunda veya rastgele örnekleme kullanıldığında
# `state`'i (durumu) ilklet
state = net.begin_state(batch_size=X.shape[0], device=device)
else:
if isinstance(net, nn.Module) and not isinstance(state, tuple):
# `state` (durum) `nn.GRU` için bir tensördür
state.detach_()
else:
# `state`, `nn.LSTM` ve özel sıfırdan uygulamamız için
# bir tensör grubudur
for s in state:
s.detach_()
y = Y.T.reshape(-1)
X, y = X.to(device), y.to(device)
y_hat, state = net(X, state)
l = loss(y_hat, y.long()).mean()
if isinstance(updater, torch.optim.Optimizer):
updater.zero_grad()
l.backward()
grad_clipping(net, 1)
updater.step()
else:
l.backward()
grad_clipping(net, 1)
# `mean` işlevi çağrıldığından dolayı
updater(batch_size=1)
metric.add(l * y.numel(), y.numel())
return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()
#@save
def train_epoch_ch8(net, train_iter, loss, updater, use_random_iter):
"""Bir modeli bir dönem içinde eğitin (Bölüm 8'de tanımlanmıştır)."""
state, timer = None, d2l.Timer()
metric = d2l.Accumulator(2) # Eğitim kaybı toplamı, andıç sayısı
for X, Y in train_iter:
if state is None or use_random_iter:
# İlk yineleme olduğunda veya rastgele örnekleme kullanıldığında
# `state`'i (durumu) ilklet
state = net.begin_state(batch_size=X.shape[0], dtype=tf.float32)
with tf.GradientTape(persistent=True) as g:
y_hat, state = net(X, state)
y = tf.reshape(tf.transpose(Y), (-1))
l = loss(y, y_hat)
params = net.trainable_variables
grads = g.gradient(l, params)
grads = grad_clipping(grads, 1)
updater.apply_gradients(zip(grads, params))
# Varsayılan Keras kaybı, bir minigrup içindeki ortalama
# kaybı döndürür
# l_sum = l * float(d2l.size(y)) if isinstance(
# loss, tf.keras.losses.Loss) else tf.reduce_sum(l)
metric.add(l * d2l.size(y), d2l.size(y))
return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()
Eğitim fonksiyonu sıfırdan veya üst düzey API’ler kullanılarak uygulanan bir RNN modelini destekler.
def train_ch8(net, train_iter, vocab, lr, num_epochs, device, #@save
use_random_iter=False):
"""Bir modeli eğitin (Bölüm 8'de tanımlanmıştır)."""
loss = gluon.loss.SoftmaxCrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',
legend=['train'], xlim=[10, num_epochs])
# İlkleyin
if isinstance(net, gluon.Block):
net.initialize(ctx=device, force_reinit=True,
init=init.Normal(0.01))
trainer = gluon.Trainer(net.collect_params(),
'sgd', {'learning_rate': lr})
updater = lambda batch_size: trainer.step(batch_size)
else:
updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)
predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device)
# Eğit ve tahminle
for epoch in range(num_epochs):
ppl, speed = train_epoch_ch8(
net, train_iter, loss, updater, device, use_random_iter)
if (epoch + 1) % 10 == 0:
animator.add(epoch + 1, [ppl])
print(f'perplexity {ppl:.1f}, {speed:.1f} tokens/sec on {str(device)}')
print(predict('time traveller'))
print(predict('traveller'))
#@save
def train_ch8(net, train_iter, vocab, lr, num_epochs, device,
use_random_iter=False):
"""Bir modeli eğitin (Bölüm 8'de tanımlanmıştır)."""
loss = nn.CrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',
legend=['train'], xlim=[10, num_epochs])
# İlkleyin
if isinstance(net, nn.Module):
updater = torch.optim.SGD(net.parameters(), lr)
else:
updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)
predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device)
# Eğit ve tahminle
for epoch in range(num_epochs):
ppl, speed = train_epoch_ch8(
net, train_iter, loss, updater, device, use_random_iter)
if (epoch + 1) % 10 == 0:
print(predict('time traveller'))
animator.add(epoch + 1, [ppl])
print(f'perplexity {ppl:.1f}, {speed:.1f} tokens/sec on {str(device)}')
print(predict('time traveller'))
print(predict('traveller'))
#@save
def train_ch8(net, train_iter, vocab, lr, num_epochs, strategy,
use_random_iter=False):
"""Bir modeli eğitin (Bölüm 8'de tanımlanmıştır)."""
with strategy.scope():
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
updater = tf.keras.optimizers.SGD(lr)
animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',
legend=['train'], xlim=[10, num_epochs])
predict = lambda prefix: predict_ch8(prefix, 50, net, vocab)
# Eğit ve tahminle
for epoch in range(num_epochs):
ppl, speed = train_epoch_ch8(net, train_iter, loss, updater,
use_random_iter)
if (epoch + 1) % 10 == 0:
print(predict('time traveller'))
animator.add(epoch + 1, [ppl])
device = d2l.try_gpu()._device_name
print(f'perplexity {ppl:.1f}, {speed:.1f} tokens/sec on {str(device)}')
print(predict('time traveller'))
print(predict('traveller'))
Şimdi RNN modelini eğitebiliriz. Veri kümesinde yalnızca 10000 andıç kullandığımızdan, modelin daha iyi yakınsaması için daha fazla döneme ihtiyacı var.
num_epochs, lr = 500, 1
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())
perplexity 1.1, 34268.1 tokens/sec on gpu(0)
time traveller for so it will be convenient to speak of himwas e
travelleryou can show black is white by argument said filby
num_epochs, lr = 500, 1
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())
perplexity 1.0, 68806.3 tokens/sec on cuda:0
time travelleryou can show black is white by argument said filby
travelleryou can show black is white by argument said filby
num_epochs, lr = 500, 1
train_ch8(net, train_iter, vocab, lr, num_epochs, strategy)
perplexity 1.1, 10524.6 tokens/sec on /GPU:0
time travelleryou can show black is white by argument said filby
travellerybut af when twon a matte mither has a sove time u
Son olarak, rastgele örnekleme yöntemini kullanmanın sonuçlarını kontrol edelim.
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,
init_rnn_state, rnn)
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu(),
use_random_iter=True)
perplexity 1.4, 34394.2 tokens/sec on gpu(0)
time travellerit s against reason said filbywas a guten flomm ha
travellerit s against reason said filbywas a guten flomm ha
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,
init_rnn_state, rnn)
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu(),
use_random_iter=True)
perplexity 1.4, 52954.3 tokens/sec on cuda:0
time travellerit s against reason said filbywhar gethematical pi
travellerit s against reason said filbywhar gethematical pi
with strategy.scope():
net = RNNModelScratch(len(vocab), num_hiddens, init_rnn_state, rnn,
get_params)
train_ch8(net, train_iter, vocab_random_iter, lr, num_epochs, strategy,
use_random_iter=True)
perplexity 1.3, 9260.5 tokens/sec on /GPU:0
time travellerit s against reason said filbywhre time traveller
traveller held in his hand was a glitteringmetallic framewo
Yukarıdaki RNN modelini sıfırdan uygulamak öğretici olsa da, pek de uygun değildir. Bir sonraki bölümde, RNN modelinin nasıl geliştirileceğini göreceğiz, örneğin nasıl daha kolay uygulanmasını ve daha hızlı çalışmasını sağlarız.
8.5.7. Özet¶
Kullanıcı tarafından sağlanan metin önekini takip eden metin üretmek için RNN tabanlı karakter düzeyinde bir dil modeli eğitebiliriz.
Basit bir RNN dil modeli girdi kodlama, RNN modelleme ve çıktı üretme içerir.
RNN modellerinin eğitim için durum ilklenmesi gerekir, ancak rasgele örnekleme ve sıralı bölümleme farklı yollar kullanır.
Sıralı bölümleme kullanırken, hesaplama maliyetini azaltmak için gradyanı koparıp ayırmamız gerekir.
Isınma süresi, herhangi bir tahmin yapmadan önce bir modelin kendisini güncellemesine (örneğin, ilkleme değerinden daha iyi bir gizli durum elde etmesine) olanak tanır.
Gradyan kırpma gradyan patlamasını önler, ancak kaybolan gradyanları düzeltemez.
8.5.8. Alıştırmalar¶
Bire bir kodlamanın, her nesne için farklı bir gömme seçmeye eşdeğer olduğunu gösterin.
Şaşkınlığı iyileştirmek için hiper parametreleri (örn. dönemlerin sayısı, gizli birimlerin sayısı, bir minigruptaki zaman adımlarının sayısı ve öğrenme oranı) ayarlayın.
Ne kadar aşağıya düşebilirsiniz?
Bire bir kodlamayı öğrenilebilir gömmelerle değiştirin. Bu daha iyi bir performansa yol açar mı?
H. G. Wells’in, örn. *Dünyalar Savaşı* gibi, diğer kitaplarıyla ne kadar iyi çalışacaktır?
En olası sonraki karakteri seçmek yerine örnekleme kullanarak tahmin işlevini değiştirin.
Ne olur?
Modeli, örneğin \(\alpha > 1\) için \(q(x_t \mid x_{t-1}, \ldots, x_1) \propto P(x_t \mid x_{t-1}, \ldots, x_1)^\alpha\)’ten örnekleme yaparak daha olası çıktılara doğru yanlı yapın.
Gradyan kırpmadan bu bölümdeki kodu çalıştırın. Ne olur?
Sıralı bölümlemeyi gizli durumları hesaplama çizgesinden ayırmayacak şekilde değiştirin. Çalışma süresi değişiyor mu? Şaşkınlığa ne olur?
Bu bölümde kullanılan etkinleştirme işlevini ReLU ile değiştirin ve bu bölümdeki deneyleri tekrarlayın. Hala gradyan kırpmaya ihtiyacımız var mı? Neden?