3.2. Sıfırdan Doğrusal Regresyon Uygulaması Yaratma¶ Open the notebook in SageMaker Studio Lab
Artık doğrusal regresyonun arkasındaki temel fikirleri anladığınıza göre, işe ellerimizi daldırarak kodda uygulamaya başlayabiliriz. Bu bölümde, veri komut işleme hattı (pipeline), model, kayıp fonksiyonu ve minigrup rasgele gradyan iniş eniyileyici dahil olmak üzere tüm yöntemi sıfırdan uygulayacağız. Modern derin öğrenme çerçeveleri neredeyse tüm bu çalışmayı otomatikleştirebilirken, bir şeyleri sıfırdan uygulamak, ne yaptığınızı gerçekten bildiğinizden emin olmanın tek yoludur. Dahası, modelleri ihtiyacımıza göre özelleştirken, kendi katmanlarımızı veya kayıp işlevlerimizi tanımlama zamanı geldiğinde, kaputun altında işlerin nasıl ilerlediğini anlamak kullanışlı olacaktır. Bu bölümde, sadece tensörlere ve otomatik türev almaya güveneceğiz. Daha sonra, derin öğrenme çerçevelerinin çekici ek özelliklerden yararlanarak daha kısa bir uygulama sunacağız.
%matplotlib inline
import random
from d2l import mxnet as d2l
from mxnet import autograd, np, npx
npx.set_np()
%matplotlib inline
import random
import torch
from d2l import torch as d2l
%matplotlib inline
import random
import tensorflow as tf
from d2l import tensorflow as d2l
3.2.1. Veri Kümesini Oluşturma¶
İşleri basitleştirmek için, gürültülü doğrusal bir model için yapay bir veri kümesi oluşturacağız. Görevimiz, veri kümemizde bulunan sonlu örnek kümesini kullanarak bu modelin parametrelerini elde etmek olacaktır. Verileri düşük boyutlu tutacağız, böylece kolayca görselleştirebiliriz. Aşağıdaki kod parçacığında, her biri standart bir normal dağılımdan örneklenmiş 2 öznitelikten oluşan 1000 örnekli bir veri kümesi oluşturuyoruz. Böylece, sentetik veri kümemiz matris \(\mathbf{X}\in \mathbb{R}^{1000 \times 2}\) olacaktır.
Veri kümemizi oluşturan gerçek parametreler \(\mathbf{w} = [2, -3.4]^\top\) ve \(b = 4.2\) olacaktır ve sentetik etiketlerimiz aşağıdaki \(\epsilon\) gürültü terimli doğrusal modele göre atanacaktır:
\(\epsilon\)’u öznitelikler ve etiketlerdeki olası ölçüm hatalarını yakalıyor diye düşünebilirsiniz. Standart varsayımların geçerli olduğunu ve böylece \(\epsilon\) değerinin ortalaması 0 olan normal bir dağılıma uyduğunu varsayacağız. Problemimizi kolaylaştırmak için, standart sapmasını 0.01 olarak ayarlayacağız. Aşağıdaki kod, sentetik veri kümemizi üretir.
def synthetic_data(w, b, num_examples): #@save
"""Veri yaratma, y = Xw + b + gürültü."""
X = np.random.normal(0, 1, (num_examples, len(w)))
y = np.dot(X, w) + b
y += np.random.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
true_w = np.array([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
def synthetic_data(w, b, num_examples): #@save
"""Veri yaratma, y = Xw + b + gürültü."""
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
def synthetic_data(w, b, num_examples): #@save
"""Veri yaratma, y = Xw + b + gürültü."""
X = tf.zeros((num_examples, w.shape[0]))
X += tf.random.normal(shape=X.shape)
y = tf.matmul(X, tf.reshape(w, (-1, 1))) + b
y += tf.random.normal(shape=y.shape, stddev=0.01)
y = tf.reshape(y, (-1, 1))
return X, y
true_w = tf.constant([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
features
’deki (öznitelikleri tutan değişken) her satırın 2 boyutlu
bir veri örneğinden oluştuğuna ve labels
’deki (etiketleri tutan
değişken) her satırın 1 boyutlu bir etiket değerinden (bir skaler)
oluştuğuna dikkat edin.
print('oznitelikler:', features[0],'\netiket:', labels[0])
oznitelikler: [2.2122064 1.1630787]
etiket: [4.662078]
print('oznitelikler:', features[0],'\netiket:', labels[0])
oznitelikler: tensor([0.1546, 2.3925])
etiket: tensor([-3.6205])
print('oznitelikler:', features[0],'\netiket:', labels[0])
oznitelikler: tf.Tensor([ 1.0085982 -0.9955097], shape=(2,), dtype=float32)
etiket: tf.Tensor([9.597065], shape=(1,), dtype=float32)
İkinci öznitelik features[:, 1]
ve labels
kullanılarak bir
dağılım grafiği oluşturup ikisi arasındaki doğrusal korelasyonu net bir
şekilde gözlemleyebiliriz.
d2l.set_figsize()
# İki nokta üstüste sadece gösterim amaçlıdır
d2l.plt.scatter(features[:, 1].asnumpy(), labels.asnumpy(), 1);
d2l.set_figsize()
# İki nokta üstüste sadece gösterim amaçlıdır
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1);
d2l.set_figsize()
# İki nokta üstüste sadece gösterim amaçlıdır
d2l.plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);
3.2.2. Veri Kümesini Okuma¶
Model eğitimlerinin, veri kümesi üzerinde birden çok geçiş yapmaktan, her seferde bir minigrup örnek almaktan ve bunları modelimizi güncellemek için kullanmaktan oluştuğunu hatırlayın. Bu süreç, makine öğrenmesi algoritmalarını eğitmek için çok temel olduğundan, veri kümesini karıştırmak ve ona minigruplar halinde erişmek için bir yardımcı işlev tanımlamaya değer.
Aşağıdaki kodda, bu işlevselliğin olası bir uygulamasını göstermek için
data_iter
işlevini tanımlıyoruz. Fonksiyon, bir grup boyutunu, bir
öznitelik matrisini ve bir etiket vektörünü alarak batch_size
boyutundaki minigrupları verir. Her bir minigrup, bir dizi öznitelik ve
etiketten oluşur.
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# Örnekler belirli bir sıra gözetmeksizin rastgele okunur
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
batch_indices = np.array(
indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices]
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# Örnekler belirli bir sıra gözetmeksizin rastgele okunur
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices]
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# Örnekler belirli bir sıra gözetmeksizin rastgele okunur
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
j = tf.constant(indices[i: min(i + batch_size, num_examples)])
yield tf.gather(features, j), tf.gather(labels, j)
Genel olarak, paralelleştirme işlemlerinde mükemmel olan GPU donanımından yararlanmak için makul boyutta minigruplar kullanmak istediğimizi unutmayın. Her örnek, modellerimiz üzerinden paralel olarak beslenebildiği ve her örnek için kayıp fonksiyonunun gradyanı da paralel olarak alınabildiğinden, GPU’lar, yüzlerce örneği yalnızca tek bir örneği işlemek için gerekebileceğinden çok daha az kısa sürede işlememize izin verir.
Biraz sezgi oluşturmak için, ilk olarak küçük bir grup veri örneği
okuyup yazdıralım. Her minigruptaki özniteliklerin şekli bize hem
minigrup boyutunu hem de girdi özniteliklerinin sayısını söyler. Aynı
şekilde, minigrubumuzun etiketleri de batch_size
ile verilen şekle
sahip olacaktır.
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
[[-1.6242899 -3.2149546 ]
[ 1.5702168 1.11278 ]
[-0.36137256 0.98650014]
[ 1.317333 0.63940585]
[-2.1471155 -0.6573725 ]
[-1.4225755 -1.1081946 ]
[-1.5367762 1.0635569 ]
[-1.48432 1.0816108 ]
[-0.1742568 1.9691626 ]
[-0.38354877 1.6984322 ]]
[[11.875465 ]
[ 3.5531938 ]
[ 0.11717813]
[ 4.6646976 ]
[ 2.157684 ]
[ 5.1158175 ]
[-2.4930956 ]
[-2.441576 ]
[-2.8460252 ]
[-2.335064 ]]
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
tensor([[ 1.4869, 1.5494],
[-0.8690, -0.0986],
[ 1.5624, 0.0090],
[-0.4172, 0.9719],
[ 0.3500, -0.1156],
[-0.3433, 2.4511],
[-1.1783, -1.2873],
[-1.1068, -0.5236],
[ 0.8041, 0.2262],
[-2.2314, 0.0541]])
tensor([[ 1.9146],
[ 2.7896],
[ 7.2889],
[ 0.0441],
[ 5.3029],
[-4.8102],
[ 6.2084],
[ 3.7547],
[ 5.0364],
[-0.4410]])
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
tf.Tensor(
[[ 1.8249083 0.43419597]
[ 0.7772833 0.6235751 ]
[-0.7631955 0.08624952]
[ 0.5574609 1.3876617 ]
[ 0.17594892 1.5851324 ]
[ 0.21412234 -1.7341554 ]
[ 0.06975526 0.54019475]
[-2.0243845 0.78842735]
[-1.2418027 0.4114364 ]
[ 0.39266726 -1.0444765 ]], shape=(10, 2), dtype=float32)
tf.Tensor(
[[ 6.3809032 ]
[ 3.630106 ]
[ 2.377214 ]
[ 0.59058595]
[-0.8450924 ]
[10.518543 ]
[ 2.5056705 ]
[-2.541665 ]
[ 0.30798355]
[ 8.541502 ]], shape=(10, 1), dtype=float32)
Yinelemeyi çalıştırırken, tüm veri kümesi tükenene kadar art arda farklı minigruplar elde ederiz (bunu deneyin). Yukarıda uygulanan yineleme, eğitici amaçlar için iyi olsa da, gerçek problemlerde bizim başımızı belaya sokacak şekilde verimsizdir. Örneğin, tüm verileri belleğe yüklememizi ve çok sayıda rastgele bellek erişimi gerçekleştirmemizi gerektirir. Derin öğrenme çerçevesinde uygulanan yerleşik yineleyiciler önemli ölçüde daha verimlidir ve hem dosyalarda depolanan verilerle hem de veri akışları aracılığıyla beslenen verilerle ilgilenebilirler.
3.2.3. Model Parametrelerini İlkleme¶
Modelimizin parametrelerini minigrup rasgele gradyan inişiyle optimize etmeye başlamadan önce, ilk olarak bazı parametrelere ihtiyacımız var. Aşağıdaki kodda, ağırlıkları, ortalaması 0 ve standart sapması 0.01 olan normal bir dağılımdan rasgele sayılar örnekleyerek ve ek girdiyi 0 olarak ayarlayarak ilkliyoruz.
w = np.random.normal(0, 0.01, (2, 1))
b = np.zeros(1)
w.attach_grad()
b.attach_grad()
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
w = tf.Variable(tf.random.normal(shape=(2, 1), mean=0, stddev=0.01),
trainable=True)
b = tf.Variable(tf.zeros(1), trainable=True)
Parametrelerimizi ilkledikten sonra, bir sonraki görevimiz, verilerimize yeterince iyi uyum sağlayana kadar onları güncellemektir. Her güncelleme, parametrelere göre kayıp fonksiyonumuzun gradyanını almayı gerektirir. Gradyan verildiğinde, her parametreyi kaybı azaltabilecek yönde güncelleyebiliriz.
Hiç kimse gradyanları açıkça hesaplamak istemediğinden (bu sıkıcı ve hataya açıktır), gradyanı hesaplamak için Section 2.5 içinde tanıtıldığı gibi otomatik türev almayı kullanırız.
3.2.4. Modeli Tanımlama¶
Daha sonra, modelimizi, onun girdileri ve parametreleri çıktıları ile ilişkilendirerek tanımlamalıyız. Doğrusal modelin çıktısını hesaplamak için, \(\mathbf{X}\) girdi özniteliklerinin ve \(\mathbf{w}\) model ağırlıklarının matris vektör nokta çarpımını alıp her bir örneğe \(b\) ek girdisini eklediğimizi hatırlayın. Aşağıda \(\mathbf{Xw}\) bir vektör ve \(b\) bir skalerdir. Yayma mekanizmasının şurada açıklandığı anımsayalım Section 2.1.3. Bir vektör ve bir skaleri topladığımızda, skaler vektörün her bileşenine eklenir.
def linreg(X, w, b): #@save
"""Doğrusal regresyon modeli."""
return np.dot(X, w) + b
def linreg(X, w, b): #@save
"""Doğrusal regresyon modeli."""
return torch.matmul(X, w) + b
def linreg(X, w, b): #@save
"""Doğrusal regresyon modeli."""
return tf.matmul(X, w) + b
3.2.5. Kayıp Fonksiyonunu Tanımlama¶
Modelimizi güncellemek, kayıp fonksiyonumuzun gradyanını almayı
gerektirdiğinden, önce kayıp fonksiyonunu tanımlamalıyız. Burada kare
kayıp fonksiyonunu şurada açıklandığı,
Section 3.1, gibi kullanacağız . Uygulamada, y
gerçek değerini tahmin edilen değer y_hat
şekline dönüştürmemiz
gerekir. Aşağıdaki işlev tarafından döndürülen sonuç da y_hat
ile
aynı şekle sahip olacaktır.
def squared_loss(y_hat, y): #@save
"""Kare kayıp."""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
def squared_loss(y_hat, y): #@save
"""Kare kayıp."""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
def squared_loss(y_hat, y): #@save
"""Kare kayıp."""
return (y_hat - tf.reshape(y, y_hat.shape)) ** 2 / 2
3.2.6. Optimizasyon Algoritmasını Tanımlama¶
Section 3.1 içinde tartıştığımız gibi, doğrusal regresyon kapalı biçim bir çözüme sahiptir. Ancak, bu doğrusal regresyon hakkında bir kitap değil: Derin öğrenme hakkında bir kitap. Bu kitabın tanıttığı diğer modellerin hiçbiri analitik olarak çözülemediğinden, bu fırsatı minigrup rasgele gradyan inişinin ilk çalışan örneğini tanıtmak için kullanacağız.
Her adımda, veri kümemizden rastgele alınan bir minigrup kullanarak,
parametrelerimize göre kaybın gradyanını tahmin edeceğiz. Daha sonra
kayıpları azaltabilecek yönde parametrelerimizi güncelleyeceğiz.
Aşağıdaki kod, bir küme parametre, bir öğrenme oranı ve bir grup boyutu
verildiğinde minigrup rasgele gradyan iniş güncellemesini uygular.
Güncelleme adımının boyutu, öğrenme oranı lr
tarafından belirlenir.
Kaybımız, örneklerin minigrubu üzerinden bir toplam olarak
hesaplandığından, adım boyutumuzu grup boyutuna (batch_size
) göre
normalleştiririz, böylece tipik bir adım boyutunun büyüklüğü, grup
boyutu seçimimize büyük ölçüde bağlı olmaz.
def sgd(params, lr, batch_size): #@save
"""Minigrup rasgele gradyan inişi."""
for param in params:
param[:] = param - lr * param.grad / batch_size
def sgd(params, lr, batch_size): #@save
"""Minigrup rasgele gradyan inişi."""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
def sgd(params, grads, lr, batch_size): #@save
"""Minigrup rasgele gradyan inişi."""
for param, grad in zip(params, grads):
param.assign_sub(lr*grad/batch_size)
3.2.7. Eğitim¶
Artık tüm parçaları yerine koyduğumuza göre, ana eğitim döngüsünü uygulamaya hazırız. Bu kodu anlamanız çok önemlidir çünkü derin öğrenmede kariyeriniz boyunca neredeyse aynı eğitim döngülerini tekrar tekrar göreceksiniz.
Her yinelemede, bir minigrup eğitim örneği alacağız ve bir dizi tahmin
elde etmek için bunları modelimizden geçireceğiz. Kaybı hesapladıktan
sonra, her parametreye göre gradyanları depolayarak ağ üzerinden geriye
doğru geçişi başlatırız. Son olarak, model parametrelerini güncellemek
için optimizasyon algoritması sgd
’yi çağıracağız.
Özetle, aşağıdaki döngüyü uygulayacağız:
\((\mathbf {w}, b)\) parametrelerini ilkletin.
Tamamlanana kadar tekrarlayın
Gradyanı hesaplayın: \(\mathbf{g} \leftarrow \partial_{(\mathbf{w},b)} \frac{1}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} l(\mathbf{x}^{(i)}, y^{(i)}, \mathbf{w}, b)\)
Parametreleri güncelleyin: \((\mathbf{w}, b) \leftarrow (\mathbf{w}, b) - \eta \mathbf{g}\)
Her bir dönemde (epoch), eğitim veri kümesindeki her örnekten
geçtikten sonra (örneklerin sayısının grup boyutuna bölünebildiği
varsayılarak) tüm veri kümesini (data_iter
işlevini kullanarak)
yineleyeceğiz. Dönemlerin sayısı, num_epochs
, ve öğrenme hızı,
lr
, burada sırasıyla 3 ve 0.03 olarak belirlediğimiz hiper
parametrelerdir. Ne yazık ki, hiper parametrelerin belirlenmesi zordur
ve deneme yanılma yoluyla bazı ayarlamalar gerektirir. Bu ayrıntıları
şimdilik atlıyoruz, ancak daha sonra Section 11
içinde tekrarlayacağız.
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
with autograd.record():
l = loss(net(X, w, b), y) # `X` ve `y`'deki minigrup kaybı
# `l`'nin şekli (`batch_size`, 1) olduğu ve skaler bir değişken olmadığı için,
# `l`'deki öğeler,[`w`, `b`]'ye göre gradyanların olduğu yeni bir değişken
# elde etmek için birbirine eklenir.
l.backward()
sgd([w, b], lr, batch_size) # Parametreleri gradyanlarına göre güncelle
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
[21:55:08] src/base.cc:49: GPU context requested, but no GPUs found.
epoch 1, loss 0.025044
epoch 2, loss 0.000090
epoch 3, loss 0.000051
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # `X` ve `y`'deki minigrup kaybı
# [`w`, `b`]'e göre `l` üzerindeki gradyanı hesaplayın
l.sum().backward()
sgd([w, b], lr, batch_size) # Parametreleri gradyanlarına göre güncelle
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
epoch 1, loss 0.035539
epoch 2, loss 0.000133
epoch 3, loss 0.000050
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
with tf.GradientTape() as g:
l = loss(net(X, w, b), y) # `X` ve `y`'deki minigrup kaybı
# [`w`, `b`]'e göre `l` üzerindeki gradyanı hesaplayın
dw, db = g.gradient(l, [w, b])
# Parametreleri gradyanlarına göre güncelle
sgd([w, b], [dw, db], lr, batch_size)
train_l = loss(net(features, w, b), labels)
print(f'donem {epoch + 1}, kayip {float(tf.reduce_mean(train_l)):f}')
donem 1, kayip 0.051920
donem 2, kayip 0.000222
donem 3, kayip 0.000052
Bu durumda, veri kümemizi kendimiz sentezlediğimiz için, gerçek parametrelerin ne olduğunu tam olarak biliyoruz. Böylece eğitimdeki başarımızı, gerçek parametreleri eğitim döngümüz aracılığıyla öğrendiklerimizle karşılaştırarak değerlendirebiliriz. Gerçekten de birbirlerine çok yakın oldukları ortaya çıkıyor.
print(f'w tahminindeki hata: {true_w - w.reshape(true_w.shape)}')
print(f'b tahminindeki hata: {true_b - b}')
w tahminindeki hata: [0.00038254 0.00028729]
b tahminindeki hata: [-4.6253204e-05]
print(f'w tahminindeki hata: {true_w - w.reshape(true_w.shape)}')
print(f'b tahminindeki hata: {true_b - b}')
w tahminindeki hata: tensor([0.0002, 0.0002], grad_fn=<SubBackward0>)
b tahminindeki hata: tensor([-4.7684e-07], grad_fn=<RsubBackward1>)
print(f'w tahminindeki hata: {true_w - tf.reshape(w, true_w.shape)}')
print(f'b tahminindeki hata: {true_b - b}')
w tahminindeki hata: [-0.0001471 -0.00130081]
b tahminindeki hata: [0.00119877]
Parametreleri mükemmel bir şekilde elde ettiğimizi kabullenmememiz gerektiğini unutmayın. Bununla birlikte, makine öğrenmesinde, genellikle temeldeki gerçek parametreleri elde etmek ile daha az ilgileniriz ve yüksek derecede doğru tahminlere yol açan parametrelerle daha çok ilgileniriz. Neyse ki, zorlu optimizasyon problemlerinde bile, rasgele gradyan inişi, kısmen derin ağlar için, oldukça doğru tahmine götüren parametrelerin birçok konfigürasyonunun mevcut olmasından dolayı, genellikle dikkate değer ölçüde iyi çözümler bulabilir.
3.2.8. Özet¶
Derin bir ağın, katmanları veya süslü optimize edicileri tanımlamaya gerek kalmadan sadece tensörler ve otomatik türev alma kullanarak nasıl sıfırdan uygulanabileceğini ve optimize edilebileceğini gördük.
Bu bölüm sadece mümkün olanın yüzeyine ışık tutar. İlerideki bölümlerde, yeni tanıttığımız kavramlara dayalı yeni modelleri açıklayacak ve bunları daha kısaca nasıl uygulayacağımızı öğreneceğiz.
3.2.9. Alıştırmalar¶
Ağırlıkları sıfıra ilkleseydik ne olurdu? Algoritma yine de çalışır mıydı?
Voltaj ve akım arasında bir model bulmaya çalışan Georg Simon Ohm olduğunuzu varsayın. Modelinizin parametrelerini öğrenmek için otomatik türev almayı kullanabilir misiniz?
Spektral enerji yoğunluğunu kullanarak bir nesnenin sıcaklığını belirlemek için Planck Yasası’nı kullanabilir misiniz?
İkinci türevleri hesaplamak isterseniz karşılaşabileceğiniz sorunlar nelerdir? Onları nasıl düzeltirsiniz?
squared_loss
işlevindereshape
işlevi neden gereklidir?Kayıp işlevi değerinin ne kadar hızlı düştüğünü bulmak için farklı öğrenme oranları kullanarak deney yapın.
Örneklerin sayısı parti boyutuna bölünemezse,
data_iter
işlevinin davranışına ne olur?