2.3. Doğrusal Cebir¶ Open the notebook in SageMaker Studio Lab
Şimdi verileri saklayabileceğinize ve oynama yapabileceğinize göre, bu kitapta yer alan modellerin çoğunu anlamanız ve uygulamanızda gereken temel doğrusal cebir bilgilerini kısaca gözden geçirelim. Aşağıda, doğrusal cebirdeki temel matematiksel nesneleri, aritmetiği ve işlemleri tanıtarak, bunların her birini matematiksel gösterim ve koddaki ilgili uygulama ile ifade ediyoruz.
2.3.1. Sayıllar¶
Doğrusal cebir veya makine öğrenmesi üzerine hiç çalışmadıysanız, matematikle ilgili geçmiş deneyiminiz muhtemelen bir seferde bir sayı düşünmekten ibaretti. Ayrıca, bir çek defterini dengelediyseniz veya hatta bir restoranda akşam yemeği için ödeme yaptıysanız, bir çift sayıyı toplama ve çarpma gibi temel şeyleri nasıl yapacağınızı zaten biliyorsunuzdur. Örneğin, Palo Alto’daki sıcaklık \(52\) Fahrenheit derecedir. Usul olarak, sadece bir sayısal miktar içeren değerlere sayıl (skaler) diyoruz. Bu değeri Celsius’a (metrik sistemin daha anlamlı sıcaklık ölçeği) dönüştürmek istiyorsanız, \(f\)’i \(52\) olarak kurup \(c = \frac{5}{9}(f - 32)\) ifadesini hesaplarsınız. Bu denklemde —\(5\), \(9\) ve \(32\)— terimlerinin her biri skaler değerlerdir. \(c\) ve \(f\) göstermelik ifadelerine (placeholders) değişkenler denir ve bilinmeyen skaler değerleri temsil ederler.
Bu kitapta, skaler değişkenlerin normal küçük harflerle (ör. \(x\), \(y\) ve \(z\)) gösterildiği matematiksel gösterimi kabul ediyoruz. Tüm (sürekli) gerçel değerli skalerlerin alanını \(\mathbb{R}\) ile belirtiyoruz. Amaca uygun olarak, tam olarak uzayın ne olduğunu titizlikle tanımlayacağız, ancak şimdilik sadece \(x \in \mathbb{R}\) ifadesinin \(x\) değerinin gerçel değerli olduğunu söylemenin usula uygun bir yolu olduğunu unutmayın. \(\in\) sembolü “içinde” olarak telaffuz edilebilir ve sadece bir kümeye üyeliği belirtir. Benzer şekilde, \(x\) ve \(y\)’nin değerlerinin yalnızca \(0\) veya \(1\) olabilen rakamlar olduğunu belirtmek için \(x, y \in \{0, 1 \}\) yazabiliriz.
Skaler, sadece bir elemente sahip bir tensör ile temsil edilir. Sıradaki kod parçasında, iki skalere değer atıyoruz ve onlarla toplama, çarpma, bölme ve üs alma gibi bazı tanıdık aritmetik işlemleri gerçekleştiriyoruz.
from mxnet import np, npx
npx.set_np()
x = np.array(3.0)
y = np.array(2.0)
x + y, x * y, x / y, x ** y
(array(5.), array(6.), array(1.5), array(9.))
import torch
x = torch.tensor(3.0)
y = torch.tensor(2.0)
x + y, x * y, x / y, x**y
(tensor(5.), tensor(6.), tensor(1.5000), tensor(9.))
import tensorflow as tf
x = tf.constant(3.0)
y = tf.constant(2.0)
x + y, x * y, x / y, x**y
(<tf.Tensor: shape=(), dtype=float32, numpy=5.0>,
<tf.Tensor: shape=(), dtype=float32, numpy=6.0>,
<tf.Tensor: shape=(), dtype=float32, numpy=1.5>,
<tf.Tensor: shape=(), dtype=float32, numpy=9.0>)
2.3.2. Vektörler (Yöneyler)¶
Bir vektörü basitçe skaler değerlerin bir listesi olarak düşünebilirsiniz. Bu değerlere vektörün elemanları (giriş değerleri veya bileşenleri) diyoruz. Vektörlerimiz veri kümemizdeki örnekleri temsil ettiğinde, değerleri gerçek dünyadaki önemini korur. Örneğin, bir kredinin temerrüde düşme (ödenmeme) riskini tahmin etmek için bir model geliştiriyorsak, her başvuru sahibini, gelirine, istihdam süresine, önceki temerrüt sayısına ve diğer faktörlere karşılık gelen bileşenleri olan bir vektörle ilişkilendirebiliriz. Hastanedeki hastaların potansiyel olarak yaşayabilecekleri kalp krizi riskini araştırıyor olsaydık, her hastayı bileşenleri en güncel hayati belirtilerini, kolesterol seviyelerini, günlük egzersiz dakikalarını vb. içeren bir vektörle temsil edebilirdik. Matematiksel gösterimlerde, genellikle vektörleri kalın, küçük harfler (örneğin, \(\mathbf{x}\), \(\mathbf{y}\) ve \(\mathbf{z})\) olarak göstereceğiz.
Vektörlerle tek boyutlu tensörler aracılığıyla çalışırız. Genelde tensörler, makinenizin bellek sınırlarına tabi olarak keyfi uzunluklara sahip olabilir.
x = np.arange(4)
x
array([0., 1., 2., 3.])
x = torch.arange(4)
x
tensor([0, 1, 2, 3])
x = tf.range(4)
x
<tf.Tensor: shape=(4,), dtype=int32, numpy=array([0, 1, 2, 3], dtype=int32)>
Bir vektörün herhangi bir öğesini bir altindis kullanarak tanımlayabiliriz. Örneğin, \(\mathbf{x}\)’in \(i.\) elemanını \(x_i\) ile ifade edebiliriz. \(x_i\) öğesinin bir skaler olduğuna dikkat edin, bu nedenle ondan bahsederken fontta kalın yazı tipi kullanmıyoruz. Genel literatür sütun vektörlerini vektörlerin varsayılan yönü olarak kabul eder, bu kitap da öyle kabullenir. Matematikte, \(\mathbf{x}\) vektörü şu şekilde yazılabilir:
burada \(x_1, \ldots, x_n\) vektörün elemanlarıdır. Kod olarak, herhangi bir öğeye onu tensöre indeksleyerek erişiriz.
x[3]
array(3.)
x[3]
tensor(3)
x[3]
<tf.Tensor: shape=(), dtype=int32, numpy=3>
2.3.2.1. Uzunluk, Boyutluluk ve Şekil¶
Bazı kavramları tekrar gözden geçirelim Section 2.1. Bir vektör sadece bir sayı dizisidir. Ayrıca her dizinin bir uzunluğu olduğu gibi, her vektör de bir uzunluğa sahiptir. Matematiksel gösterimde, \(\mathbf{x}\) vektörünün \(n\) gerçel değerli skalerlerden oluştuğunu söylemek istiyorsak, bunu \(\mathbf{x} \in \mathbb{R}^n\) olarak ifade edebiliriz. Bir vektörün uzunluğuna genel olarak vektörün boyutu denir.
Sıradan bir Python dizisinde olduğu gibi, Python’un yerleşik (built-in)
len()
işlevini çağırarak bir tensörün uzunluğuna erişebiliriz.
len(x)
4
len(x)
4
len(x)
4
Bir tensör bir vektörü (tam olarak bir ekseni ile) temsil ettiğinde,
uzunluğuna .shape
özelliği ile de erişebiliriz. Şekil, tensörün her
ekseni boyunca uzunluğu (boyutsallığı) listeleyen bir gruptur. Sadece
bir ekseni olan tensörler için, şeklin sadece bir elemanı vardır.
x.shape
(4,)
x.shape
torch.Size([4])
x.shape
TensorShape([4])
“Boyut” kelimesinin bu bağlamlarda aşırı yüklenme eğiliminde olduğunu ve bunun da insanları şaşırtma yöneliminde olduğunu unutmayın. Açıklığa kavuşturmak için, bir vektörün veya ekseninin boyutluluğunu onun uzunluğuna atıfta bulunmak için kullanırız; yani bir vektörün veya eksenin eleman sayısı. Halbuki, bir tensörün boyutluluğunu, bir tensörün sahip olduğu eksen sayısını ifade etmek için kullanırız. Bu anlamda, bir tensörün bazı eksenlerinin boyutluluğu, bu eksenin uzunluğu olacaktır.
2.3.3. Matrisler¶
Vektörler, skalerleri sıfırdan birinci dereceye kadar genelleştirirken, matrisler de vektörleri birinci dereceden ikinci dereceye genelleştirir. Genellikle kalın, büyük harflerle (örn., \(\mathbf{X}\), \(\mathbf{Y}\), and \(\mathbf{Z}\)) göstereceğimiz matrisler, kodda iki eksenli tensörler olarak temsil edilir.
Matematiksel gösterimde, \(\mathbf{A}\) matrisinin gerçel değerli skaler \(m\) satır ve \(n\) sütundan oluştuğunu ifade etmek için \(\mathbf{A} \in \mathbb{R}^{m \times n}\)’i kullanırız. Görsel olarak, herhangi bir \(\mathbf{A} \in \mathbb{R}^{m \times n}\) matrisini \(a_{ij}\) öğesinin \(i.\) satıra ve \(j.\) sütuna ait olduğu bir tablo olarak gösterebiliriz:
Herhangi bir \(\mathbf{A}\in \mathbb{R}^{m\times n}\) için, \(\mathbf{A}\) (\(m\), \(n\)) veya \(m\times n\) şeklindedir. Özellikle, bir matris aynı sayıda satır ve sütuna sahip olduğunda, şekli bir kareye dönüşür; dolayısıyla buna kare matris denir.
Bir tensörü örneği yaratırken, en sevdiğimiz işlevlerden herhangi birini çağırıp \(m\) ve \(n\) iki bileşeninden oluşan bir şekil belirterek \(m \times n\) matrisi oluşturabiliriz.
A = np.arange(20).reshape(5, 4)
A
array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]])
A = torch.arange(20).reshape(5, 4)
A
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19]])
A = tf.reshape(tf.range(20), (5, 4))
A
<tf.Tensor: shape=(5, 4), dtype=int32, numpy=
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19]], dtype=int32)>
Satır (\(i\)) ve sütun (\(j\)) indekslerini belirterek \(\mathbf{A}\) matrisinin \(a_{ij}\) skaler öğesine erişebiliriz, \([\mathbf{A}]_{ij}\) gibi. Bir \(\mathbf{A}\) matrisinin skaler elemanları verilmediğinde, örneğin (2.3.2) gibi, basitçe \(\mathbf{A}\) matrisinin küçük harfli altindislisi \(a_{ij}\)’yi kullanarak \([\mathbf{A}]_{ij}\)’ye atıfta bulunuruz. Gösterimi basit tutarken indeksleri ayırmak için virgüller yalnızca gerekli olduğunda eklenir, örneğin \(a_{2, 3j}\) ve \([\mathbf{A}]_{2i-1, 3}\) gibi.
Bazen eksenleri ters çevirmek isteriz. Bir matrisin satırlarını ve sütunlarını değiştirdiğimizde çıkan sonuç matrisine devrik (transpose) adı verilir. Usul olarak, bir \(\mathbf{A}\)’nin devriğini \(\mathbf{A}^\top\) ile gösteririz ve eğer \(\mathbf{B} = \mathbf{A}^\top\) ise herhangi bir \(i\) and \(j\) için \(b_{ij} = a_{ji}\)’dir. Bu nedenle, (2.3.2)’deki \(\mathbf{A}\)’nin devriği bir \(n\times m\) matristir:
Şimdi kodda bir matrisin devriğine erişiyoruz.
A.T
array([[ 0., 4., 8., 12., 16.],
[ 1., 5., 9., 13., 17.],
[ 2., 6., 10., 14., 18.],
[ 3., 7., 11., 15., 19.]])
A.T
tensor([[ 0, 4, 8, 12, 16],
[ 1, 5, 9, 13, 17],
[ 2, 6, 10, 14, 18],
[ 3, 7, 11, 15, 19]])
tf.transpose(A)
<tf.Tensor: shape=(4, 5), dtype=int32, numpy=
array([[ 0, 4, 8, 12, 16],
[ 1, 5, 9, 13, 17],
[ 2, 6, 10, 14, 18],
[ 3, 7, 11, 15, 19]], dtype=int32)>
Kare matrisin özel bir türü olarak, bir simetrik matris
\(\mathbf{A}\), devriğine eşittir:
\(\mathbf{A} = \mathbf{A}^\top\). Burada simetrik bir matrisi B
diye tanımlıyoruz.
B = np.array([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
B
array([[1., 2., 3.],
[2., 0., 4.],
[3., 4., 5.]])
B = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
B
tensor([[1, 2, 3],
[2, 0, 4],
[3, 4, 5]])
B = tf.constant([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
B
<tf.Tensor: shape=(3, 3), dtype=int32, numpy=
array([[1, 2, 3],
[2, 0, 4],
[3, 4, 5]], dtype=int32)>
Şimdi B
yi kendi devriğiyle karşılaştıralım.
B == B.T
array([[ True, True, True],
[ True, True, True],
[ True, True, True]])
B == B.T
tensor([[True, True, True],
[True, True, True],
[True, True, True]])
B == tf.transpose(B)
<tf.Tensor: shape=(3, 3), dtype=bool, numpy=
array([[ True, True, True],
[ True, True, True],
[ True, True, True]])>
Matrisler yararlı veri yapılarıdır: Farklı değişim (varyasyon) kiplerine (modalite) sahip verileri düzenlememize izin verirler. Örneğin, matrisimizdeki satırlar farklı evlere (veri örneklerine) karşılık gelirken, sütunlar farklı özelliklere karşılık gelebilir. Daha önce elektronik tablo yazılımı kullandıysanız veya şurayı okuduysanız Section 2.2, bu size tanıdık gelecektir. Bu nedenle, tek bir vektörün varsayılan yönü bir sütun vektörü olmasına rağmen, bir tablo veri kümesini temsil eden bir matriste, her veri örneğini matristeki bir satır vektörü olarak ele almak daha gelenekseldir. Ve sonraki bölümlerde göreceğimiz gibi, bu düzen ortak derin öğrenme tatbikatlarını mümkün kılacaktır. Örneğin, bir tensörün en dış ekseni boyunca, veri örneklerinin minigruplarına veya minigrup yoksa yalnızca veri örneklerine erişebilir veya bir bir sayabiliriz.
2.3.4. Tensörler¶
Vektörlerin skalerleri genellemesi ve matrislerin vektörleri genellemesi gibi, daha fazla eksenli veri yapıları oluşturabiliriz. Tensörler (bu alt bölümdeki “tensörler” cebirsel nesnelere atıfta bulunur) bize rastgele sayıda ekseni olan \(n\)-boyutlu dizileri tanımlamanın genel bir yolunu verir. Vektörler, örneğin, birinci dereceden tensörlerdir ve matrisler ikinci dereceden tensörlerdir. Tensörler, özel bir yazı tipinin büyük harfleriyle (ör. \(\mathsf{X}\), \(\mathsf{Y}\) ve \(\mathsf{Z}\)) ve indeksleme mekanizmalarıyla (ör. \(X_{ijk}\) ve \([\mathsf{X}]_{1, 2i-1, 3}\)), matrislerinkine benzer gösterilir.
Renk kanallarını (kırmızı, yeşil ve mavi) istiflemek için yükseklik, genişlik ve bir kanal eksenine karşılık gelen 3 eksene sahip \(n\)-boyutlu dizi olarak gelen imgelerle çalışmaya başladığımızda tensörler daha önemli hale gelecektir. Şimdilik, daha yüksek dereceli tensörleri atlayacağız ve temellere odaklanacağız.
X = np.arange(24).reshape(2, 3, 4)
X
array([[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]],
[[12., 13., 14., 15.],
[16., 17., 18., 19.],
[20., 21., 22., 23.]]])
X = torch.arange(24).reshape(2, 3, 4)
X
tensor([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],
[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]])
X = tf.reshape(tf.range(24), (2, 3, 4))
X
<tf.Tensor: shape=(2, 3, 4), dtype=int32, numpy=
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],
[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]], dtype=int32)>
2.3.5. Tensör Aritmetiğinin Temel Özellikleri¶
Skalerler, vektörler, matrisler ve rasgele sayıda eksenli tensörler (bu alt bölümdeki “tensörler” cebirsel nesnelere atıfta bulunur), çoğu zaman kullanışlı olan bazı güzel özelliklere sahiptir. Örneğin, bir eleman yönlü işlemin tanımından, herhangi bir eleman yönlü tekli işlemin işlenen nesnenin şeklini değiştirmediğini fark etmiş olabilirsiniz. Benzer şekilde, aynı şekle sahip herhangi bir iki tensör göz önüne alındığında, herhangi bir ikili elemanlı işlemin sonucu, gene aynı şekle sahip bir tensör olacaktır. Örneğin, aynı şekle sahip iki matris toplama, bu iki matrisin üzerinde eleman yönlü toplama gerçekleştirir.
A = np.arange(20).reshape(5, 4)
B = A.copy() # `A`nın kopyasını yeni bellek tahsis ederek `B`ye atayın
A, A + B
(array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]]),
array([[ 0., 2., 4., 6.],
[ 8., 10., 12., 14.],
[16., 18., 20., 22.],
[24., 26., 28., 30.],
[32., 34., 36., 38.]]))
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = A.clone() # `A`nın kopyasını yeni bellek tahsis ederek `B`ye atayın
A, A + B
(tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]]),
tensor([[ 0., 2., 4., 6.],
[ 8., 10., 12., 14.],
[16., 18., 20., 22.],
[24., 26., 28., 30.],
[32., 34., 36., 38.]]))
A = tf.reshape(tf.range(20, dtype=tf.float32), (5, 4))
B = A # Yeni bellek tahsis ederek `A`yı `B`ye klonlamak yok
A, A + B
(<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]], dtype=float32)>,
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[ 0., 2., 4., 6.],
[ 8., 10., 12., 14.],
[16., 18., 20., 22.],
[24., 26., 28., 30.],
[32., 34., 36., 38.]], dtype=float32)>)
Özellikle, iki matrisin eleman yönlü çarpımına Hadamard çarpımı (matematik gösterimi \(\odot\)) denir. \(i.\) satır ve \(j.\) sütununun öğesi \(b_{ij}\) olan \(\mathbf{B}\in\mathbb{R}^{m\times n}\) matrisini düşünün. \(\mathbf{A}\) ((2.3.2)’da tanımlanmıştır) ve \(\mathbf{B}\) matrislerinin Hadamard çarpımı:
A * B
array([[ 0., 1., 4., 9.],
[ 16., 25., 36., 49.],
[ 64., 81., 100., 121.],
[144., 169., 196., 225.],
[256., 289., 324., 361.]])
A * B
tensor([[ 0., 1., 4., 9.],
[ 16., 25., 36., 49.],
[ 64., 81., 100., 121.],
[144., 169., 196., 225.],
[256., 289., 324., 361.]])
A * B
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[ 0., 1., 4., 9.],
[ 16., 25., 36., 49.],
[ 64., 81., 100., 121.],
[144., 169., 196., 225.],
[256., 289., 324., 361.]], dtype=float32)>
Bir tensörün skaler ile çarpılması veya toplanması, işlenen tensörün her elemanını skaler ile toplayacağından veya çarpacağından tensörün şeklini de değiştirmez.
a = 2
X = np.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape
(array([[[ 2., 3., 4., 5.],
[ 6., 7., 8., 9.],
[10., 11., 12., 13.]],
[[14., 15., 16., 17.],
[18., 19., 20., 21.],
[22., 23., 24., 25.]]]),
(2, 3, 4))
a = 2
X = torch.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape
(tensor([[[ 2, 3, 4, 5],
[ 6, 7, 8, 9],
[10, 11, 12, 13]],
[[14, 15, 16, 17],
[18, 19, 20, 21],
[22, 23, 24, 25]]]),
torch.Size([2, 3, 4]))
a = 2
X = tf.reshape(tf.range(24), (2, 3, 4))
a + X, (a * X).shape
(<tf.Tensor: shape=(2, 3, 4), dtype=int32, numpy=
array([[[ 2, 3, 4, 5],
[ 6, 7, 8, 9],
[10, 11, 12, 13]],
[[14, 15, 16, 17],
[18, 19, 20, 21],
[22, 23, 24, 25]]], dtype=int32)>,
TensorShape([2, 3, 4]))
2.3.6. İndirgeme¶
Keyfi tensörlerle gerçekleştirebileceğimiz faydalı işlemlerden biri elemanlarının toplamını hesaplamaktır. Matematiksel gösterimde, \(\sum\) sembolünü kullanarak toplamları ifade ederiz. \(d\) uzunluğa sahip \(\mathbf{x}\) vektöründeki öğelerin toplamını ifade etmek için \(\sum_{i=1}^d x_i\) yazarız. Kodda, toplamı hesaplamak için sadece ilgili işlevi çağırabiliriz.
x = np.arange(4)
x, x.sum()
(array([0., 1., 2., 3.]), array(6.))
x = torch.arange(4, dtype=torch.float32)
x, x.sum()
(tensor([0., 1., 2., 3.]), tensor(6.))
x = tf.range(4, dtype=tf.float32)
x, tf.reduce_sum(x)
(<tf.Tensor: shape=(4,), dtype=float32, numpy=array([0., 1., 2., 3.], dtype=float32)>,
<tf.Tensor: shape=(), dtype=float32, numpy=6.0>)
Rasgele şekilli tensörlerin elamanları üzerindeki toplamları ifade edebiliriz. Örneğin, \(m\times n\) matris \(\mathbf{A}\) öğelerinin toplamı \(\sum_{i=1}^{m} \sum_{j=1}^{n} a_{ij}\) diye yazılabilir.
A.shape, A.sum()
((5, 4), array(190.))
A.shape, A.sum()
(torch.Size([5, 4]), tensor(190.))
A.shape, tf.reduce_sum(A)
(TensorShape([5, 4]), <tf.Tensor: shape=(), dtype=float32, numpy=190.0>)
Varsayılan olarak, toplamı hesaplama işlevini çağırmak, tüm eksenleri
boyunca bir tensörü skalere indirger. Toplama yoluyla tensörün
indirgendiği eksenleri de belirtebiliriz. Örnek olarak matrisleri alın.
Tüm satırların öğelerini toplayarak satır boyutunu (eksen 0) indirgemek
için, işlevi çağırırken axis=0
değerini belirtiriz. Girdi matrisi,
çıktı vektörü oluşturmak için eksen 0 boyunca indirgendiğinden, girdinin
eksen 0 boyutu, çıktının şeklinde kaybolur.
A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape
(array([40., 45., 50., 55.]), (4,))
A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape
(tensor([40., 45., 50., 55.]), torch.Size([4]))
A_sum_axis0 = tf.reduce_sum(A, axis=0)
A_sum_axis0, A_sum_axis0.shape
(<tf.Tensor: shape=(4,), dtype=float32, numpy=array([40., 45., 50., 55.], dtype=float32)>,
TensorShape([4]))
axis=1
olarak belirtmek, tüm sütunların öğelerini toplayarak sütun
boyutunu (eksen 1) azaltacaktır. Böylece, girdinin eksen 1 boyutu,
çıktının şeklinde kaybolur.
A_sum_axis1 = A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape
(array([ 6., 22., 38., 54., 70.]), (5,))
A_sum_axis1 = A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape
(tensor([ 6., 22., 38., 54., 70.]), torch.Size([5]))
A_sum_axis1 = tf.reduce_sum(A, axis=1)
A_sum_axis1, A_sum_axis1.shape
(<tf.Tensor: shape=(5,), dtype=float32, numpy=array([ 6., 22., 38., 54., 70.], dtype=float32)>,
TensorShape([5]))
Bir matrisin toplama yoluyla hem satırlar hem de sütunlar boyunca indirgenmesi, matrisin tüm öğelerinin toplanmasıyla eşdeğerdir.
A.sum(axis=[0, 1]) # `A.sum()` ile aynı
array(190.)
A.sum(axis=[0, 1]) # `A.sum()` ile aynı
tensor(190.)
tf.reduce_sum(A, axis=[0, 1]) # `tf.reduce_sum(A)` ile aynı
<tf.Tensor: shape=(), dtype=float32, numpy=190.0>
İlgili bir miktar da ortalamadır. Ortalamayı, toplamı toplam eleman sayısına bölerek hesaplıyoruz. Kod olarak, keyfi şekildeki tensörlerdeki ortalamanın hesaplanmasında ilgili işlevi çağırabiliriz.
A.mean(), A.sum() / A.size
(array(9.5), array(9.5))
A.mean(), A.sum() / A.numel()
(tensor(9.5000), tensor(9.5000))
tf.reduce_mean(A), tf.reduce_sum(A) / tf.size(A).numpy()
(<tf.Tensor: shape=(), dtype=float32, numpy=9.5>,
<tf.Tensor: shape=(), dtype=float32, numpy=9.5>)
Benzer şekilde, ortalama hesaplama fonksiyonu, belirtilen eksenler boyunca bir tensörü de indirgeyebilir.
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
(array([ 8., 9., 10., 11.]), array([ 8., 9., 10., 11.]))
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
(tensor([ 8., 9., 10., 11.]), tensor([ 8., 9., 10., 11.]))
tf.reduce_mean(A, axis=0), tf.reduce_sum(A, axis=0) / A.shape[0]
(<tf.Tensor: shape=(4,), dtype=float32, numpy=array([ 8., 9., 10., 11.], dtype=float32)>,
<tf.Tensor: shape=(4,), dtype=float32, numpy=array([ 8., 9., 10., 11.], dtype=float32)>)
2.3.6.1. İndirgemesiz Toplama¶
Gene de, bazen toplamı veya ortalamayı hesaplamak için işlevi çağırırken eksen sayısını değiştirmeden tutmak yararlı olabilir.
sum_A = A.sum(axis=1, keepdims=True)
sum_A
array([[ 6.],
[22.],
[38.],
[54.],
[70.]])
sum_A = A.sum(axis=1, keepdims=True)
sum_A
tensor([[ 6.],
[22.],
[38.],
[54.],
[70.]])
sum_A = tf.reduce_sum(A, axis=1, keepdims=True)
sum_A
<tf.Tensor: shape=(5, 1), dtype=float32, numpy=
array([[ 6.],
[22.],
[38.],
[54.],
[70.]], dtype=float32)>
Örneğin, sum_A
her satırı topladıktan sonra hala iki eksenini
koruduğundan,A
’yı yayınlayarak sum_A
ile bölebiliriz.
A / sum_A
array([[0. , 0.16666667, 0.33333334, 0.5 ],
[0.18181819, 0.22727273, 0.27272728, 0.3181818 ],
[0.21052632, 0.23684211, 0.2631579 , 0.28947368],
[0.22222222, 0.24074075, 0.25925925, 0.2777778 ],
[0.22857143, 0.24285714, 0.25714287, 0.27142859]])
A / sum_A
tensor([[0.0000, 0.1667, 0.3333, 0.5000],
[0.1818, 0.2273, 0.2727, 0.3182],
[0.2105, 0.2368, 0.2632, 0.2895],
[0.2222, 0.2407, 0.2593, 0.2778],
[0.2286, 0.2429, 0.2571, 0.2714]])
A / sum_A
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[0. , 0.16666667, 0.33333334, 0.5 ],
[0.18181819, 0.22727273, 0.27272728, 0.3181818 ],
[0.21052632, 0.23684211, 0.2631579 , 0.28947368],
[0.22222222, 0.24074075, 0.25925925, 0.2777778 ],
[0.22857143, 0.24285714, 0.25714287, 0.27142859]], dtype=float32)>
Bir eksen boyunca A
’nın öğelerinin biriktirilmiş (kümülatif)
toplamını hesaplamak istiyorsak, axis=0
diyelim (satır satır),
cumsum
işlevini çağırabiliriz. Bu işlev girdi tensörünü herhangi bir
eksen boyunca indirgemez.
A.cumsum(axis=0)
array([[ 0., 1., 2., 3.],
[ 4., 6., 8., 10.],
[12., 15., 18., 21.],
[24., 28., 32., 36.],
[40., 45., 50., 55.]])
A.cumsum(axis=0)
tensor([[ 0., 1., 2., 3.],
[ 4., 6., 8., 10.],
[12., 15., 18., 21.],
[24., 28., 32., 36.],
[40., 45., 50., 55.]])
tf.cumsum(A, axis=0)
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[ 0., 1., 2., 3.],
[ 4., 6., 8., 10.],
[12., 15., 18., 21.],
[24., 28., 32., 36.],
[40., 45., 50., 55.]], dtype=float32)>
2.3.7. Nokta Çarpımları¶
Şimdiye kadar sadece eleman yönlü işlemler, toplamalar ve ortalamalar gerçekleştirdik. Ayrıca tüm yapabileceğimiz bu olsaydı, doğrusal cebir muhtemelen kendi bölümünü hak etmeyecekti. Bununla birlikte, en temel işlemlerden biri iç çarpımdır. İki vektör \(\mathbf{x}, \mathbf{y} \in \mathbb{R}^d\) verildiğinde, iç çarpımları \(\mathbf{x}^\top \mathbf{y}\) (veya \(\langle \mathbf{x}, \mathbf{y} \rangle\)), aynı konumdaki öğelerin çarpımlarının toplamıdır: \(\mathbf{x}^\top \mathbf{y} = \sum_{i=1}^{d} x_i y_i\).
y = np.ones(4)
x, y, np.dot(x, y)
(array([0., 1., 2., 3.]), array([1., 1., 1., 1.]), array(6.))
y = torch.ones(4, dtype = torch.float32)
x, y, torch.dot(x, y)
(tensor([0., 1., 2., 3.]), tensor([1., 1., 1., 1.]), tensor(6.))
y = tf.ones(4, dtype=tf.float32)
x, y, tf.tensordot(x, y, axes=1)
(<tf.Tensor: shape=(4,), dtype=float32, numpy=array([0., 1., 2., 3.], dtype=float32)>,
<tf.Tensor: shape=(4,), dtype=float32, numpy=array([1., 1., 1., 1.], dtype=float32)>,
<tf.Tensor: shape=(), dtype=float32, numpy=6.0>)
İki vektörün nokta çarpımlarını, eleman yönlü bir çarpma ve ardından bir toplam gerçekleştirerek eşit şekilde ifade edebileceğimizi unutmayın:
np.sum(x * y)
array(6.)
torch.sum(x * y)
tensor(6.)
tf.reduce_sum(x * y)
<tf.Tensor: shape=(), dtype=float32, numpy=6.0>
Nokta çarpımları çok çeşitli bağlamlarda kullanışlıdır. Örneğin, \(\mathbf{x} \in \mathbb{R}^d\) vektörü ve \(\mathbf{w} \in \mathbb{R}^d\) ile belirtilen bir ağırlık kümesi verildiğinde, \(\mathbf{x}\) içindeki değerlerin \(\mathbf{w}\) ağırlıklarına göre ağırlıklı toplamı \(\mathbf{x}^\top \mathbf{w}\) nokta çarpımı olarak ifade edilebilir. Ağırlıklar negatif olmadığında ve bire (örn., \(\left(\sum_{i=1}^{d} {w_i} = 1\right)\)) toplandığında, nokta çarpımı ağırlıklı ortalamayı ifade eder. İki vektörü birim uzunluğa sahip olacak şekilde normalleştirdikten sonra, nokta çarpımlar arasındaki açının kosinüsünü ifade eder. Uzunluk kavramını bu bölümün ilerleyen kısımlarında usüle uygun tanıtacağız.
2.3.8. Matris-Vektör Çarpımları¶
Artık nokta çarpımlarını nasıl hesaplayacağımızı bildiğimize göre, matris-vektör çarpımlarını anlamaya başlayabiliriz. \(\mathbf{A} \in \mathbb{R}^{m \times n}\) matrisini ve \(\mathbf{x} \in \mathbb{R}^n\) vektörünü sırasıyla tanımladık ve (2.3.2) ve (2.3.1)’de görselleştirdik. \(\mathbf{A}\) matrisini satır vektörleriyle görselleştirerek başlayalım.
burada her \(\mathbf{a}^\top_{i} \in \mathbb{R}^n\), \(\mathbf{A}\) matrisinin \(i .\) satırını temsil eden bir satır vektörüdür. Matris-vektör çarpımı \(\mathbf{A}\mathbf{x}\), basitçe \(i.\) elemanı \(\mathbf{a}^\top_i \mathbf{x}\) iç çarpımı olan \(m\) uzunluğunda bir sütun vektörüdür.
\(\mathbf{A}\in \mathbb{R}^{m \times n}\) matrisi ile çarpmayı vektörleri \(\mathbb{R}^{n}\)’den \(\mathbb{R}^{m}\)’e yansıtan bir dönüşüm olarak düşünebiliriz. Bu dönüşümler oldukça faydalı oldu. Örneğin, döndürmeleri bir kare matrisle çarpma olarak gösterebiliriz. Sonraki bölümlerde göreceğimiz gibi, bir önceki katmanın değerleri göz önüne alındığında, bir sinir ağındaki her bir katman hesaplanırken gereken en yoğun hesaplamaları tanımlamak için matris-vektör çarpımlarını da kullanabiliriz.
Matris-vektör çarpımlarını tensörlerle kodda ifade ederken, nokta
çarpımlarındaki aynı dot
işlevini kullanırız. A
matrisi ve x
vektörü ile np.dot(A, x)
dediğimizde matris-vektör çarpımı
gerçekleştirilir. A
sütun boyutunun (eksen 1 boyunca uzunluğu) x
boyutuyla (uzunluğu) aynı olması gerektiğini unutmayın.
A.shape, x.shape, np.dot(A, x)
((5, 4), (4,), array([ 14., 38., 62., 86., 110.]))
Matris-vektör çarpımlarını tensörlerle kodda ifade ederken, mv
işlevini kullanırız. A
matrisi ve x
vektörü ile
torch.mv(A, x)
dediğimizde matris-vektör çarpımı gerçekleştirilir.
A
sütun boyutunun (eksen 1 boyunca uzunluğu) x
boyutuyla
(uzunluğu) aynı olması gerektiğini unutmayın.
A.shape, x.shape, torch.mv(A, x)
(torch.Size([5, 4]), torch.Size([4]), tensor([ 14., 38., 62., 86., 110.]))
Matris-vektör çarpımlarını tensörlerle kodda ifade ederken, matvec
işlevini kullanırız. A
matrisi ve x
vektörü ile
tf.linalg.matvec(A, x)
dediğimizde matris-vektör çarpımı
gerçekleştirilir. A
sütun boyutunun (eksen 1 boyunca uzunluğu) x
boyutuyla (uzunluğu) aynı olması gerektiğini unutmayın.
A.shape, x.shape, tf.linalg.matvec(A, x)
(TensorShape([5, 4]),
TensorShape([4]),
<tf.Tensor: shape=(5,), dtype=float32, numpy=array([ 14., 38., 62., 86., 110.], dtype=float32)>)
2.3.9. Matris-Matris Çarpımı¶
Eğer nokta ve matris-vektör çarpımlarını anladıysanız, matris-matris çarpımı açık olmalıdır.
\(\mathbf{A} \in \mathbb{R}^{n \times k}\) ve \(\mathbf{B} \in \mathbb{R}^{k \times m}\) diye iki matrisimizin olduğunu diyelim:
\(\mathbf{A}\) matrisinin \(i.\) satırını temsil eden satır vektörünü \(\mathbf{a}^\top_{i}\) ile belirtelim ve \(\mathbf{B}\) matrisinin \(j.\) sütunu da \(\mathbf{b}_{j}\) olsun. \(\mathbf{C} = \mathbf{A}\mathbf{B}\) matris çarpımını üretmek için \(\mathbf{A}\)’yı satır vektörleri ve \(\mathbf{B}\)’yi sütun vektörleri ile düşünmek en kolay yoldur:
Daha sonra, \(\mathbf{C} \in \mathbb{R}^{n \times m}\) matris çarpımı her bir \(c_{ij}\) öğesi \(\mathbf{a}^\top_i \mathbf{b}_j\) nokta çarpımı hesaplanarak üretilir:
Matris-matris çarpımı \(\mathbf{AB}\)’yi sadece \(m\) tane
matris-vektör çarpımı gerçekleştirmek ve sonuçları \(n\times m\)
matrisi oluşturmak için birleştirmek olarak düşünebiliriz. Aşağıdaki kod
parçasında, A
ve B
üzerinde matris çarpımı yapıyoruz. Burada
A
, 5 satır ve 4 sütunlu bir matristir ve B
4 satır ve 3 sütunlu
bir matristir. Çarpma işleminden sonra 5 satır ve 3 sütun içeren bir
matris elde ederiz.
B = np.ones(shape=(4, 3))
np.dot(A, B)
array([[ 6., 6., 6.],
[22., 22., 22.],
[38., 38., 38.],
[54., 54., 54.],
[70., 70., 70.]])
B = torch.ones(4, 3)
torch.mm(A, B)
tensor([[ 6., 6., 6.],
[22., 22., 22.],
[38., 38., 38.],
[54., 54., 54.],
[70., 70., 70.]])
B = tf.ones((4, 3), tf.float32)
tf.matmul(A, B)
<tf.Tensor: shape=(5, 3), dtype=float32, numpy=
array([[ 6., 6., 6.],
[22., 22., 22.],
[38., 38., 38.],
[54., 54., 54.],
[70., 70., 70.]], dtype=float32)>
Matris-matris çarpımı basitçe matris çarpımı olarak adlandırılabilir ve Hadamard çarpımı ile karıştırılmamalıdır.
2.3.10. Normlar (Büyüklükler)¶
Doğrusal cebirde en kullanışlı operatörlerden bazıları normlardır. Gayri resmi olarak, bir vektörün normu bize bir vektörün ne kadar büyük olduğunu söyler. Burada ele alınan ebat kavramı boyutsallık değil, bileşenlerin büyüklüğü ile ilgilidir.
Doğrusal cebirde, bir vektör normu, bir vektörü bir skalere eşleştiren ve bir avuç dolusu özelliği karşılayan bir \(f\) fonksiyonudur. Herhangi bir \(\mathbf{x}\) vektörü verildiğinde, ilk özellik, bir vektörün tüm elemanlarını sabit bir \(\alpha\) çarpanına göre ölçeklendirirsek, normunun da aynı sabit çarpanın mutlak değerine göre ölçeklendiğini söyler:
İkinci özellik, tanıdık üçgen eşitsizliğidir:
Üçüncü özellik, normun negatif olmaması gerektiğini söyler:
Çoğu bağlamda herhangi bir şey için en küçük ebat 0 olduğu için bu mantıklıdır. Nihai özellik, en küçük normun sadece ve sadece tüm sıfırlardan oluşan bir vektör tarafından elde edilmesini gerektirir.
Normların mesafe ölçülerine çok benzediğini fark edebilirsiniz. Ayrıca eğer ilkokuldan Öklid mesafesini hatırlarsanız (Pisagor teoremini düşünün), o zaman negatif olmama ve üçgen eşitsizlik kavramları zihininizde bir zil çalabilir. Aslında Öklid mesafesi bir normdur: Özellikle \(L_2\) normudur. \(n\) boyutlu vektör, \(\mathbf{x}\), içindeki öğelerin \(x_1,\ldots,x_n\) olduğunu varsayalım. \(\mathbf{x}\)’in \(L_2\) normu, vektör öğelerinin karelerinin toplamının kareköküdür:
burada \(2\) altindisi genellikle \(L_2\) normlarında atlanır, yani, \(\|\mathbf{x}\|\), \(\|\mathbf{x}\|_2\) ile eşdeğerdir. Kodda, bir vektörün \(L_2\) normunu aşağıdaki gibi hesaplayabiliriz.
u = np.array([3, -4])
np.linalg.norm(u)
array(5.)
u = torch.tensor([3.0, -4.0])
torch.norm(u)
tensor(5.)
u = tf.constant([3.0, -4.0])
tf.norm(u)
<tf.Tensor: shape=(), dtype=float32, numpy=5.0>
Derin öğrenmede, kare \(L_2\) normuyla daha sık çalışırız. Ayrıca, vektör öğelerinin mutlak değerlerinin toplamı olarak ifade edilen \(L_1\) normu ile de sık karşılaşacaksınız:
\(L_2\) normuna kıyasla, sıradışı (aykırı) değerlerden daha az etkilenir. \(L_1\) normunu hesaplamak için, elemanların toplamı üzerinde mutlak değer fonksiyonunu oluştururuz.
np.abs(u).sum()
array(7.)
torch.abs(u).sum()
tensor(7.)
tf.reduce_sum(tf.abs(u))
<tf.Tensor: shape=(), dtype=float32, numpy=7.0>
Hem \(L_2\) normu hem de \(L_1\) normu, daha genel \(L_p\) normunun özel durumlarıdır:
\(L_2\) vektör normlarına benzer bir şekilde, \(\mathbf{X} \in \mathbb{R}^{m \times n}\) matrisinin Frobenius normu, matris elemanlarının karelerin toplamının kare köküdür:
Frobenius normu, vektör normlarının tüm özelliklerini karşılar. Matris şeklindeki bir vektörün bir \(L_2\) normu gibi davranır. Aşağıdaki işlevi çağırmak, bir matrisin Frobenius normunu hesaplar.
np.linalg.norm(np.ones((4, 9)))
array(6.)
torch.norm(torch.ones((4, 9)))
tensor(6.)
tf.norm(tf.ones((4, 9)))
<tf.Tensor: shape=(), dtype=float32, numpy=6.0>
2.3.10.1. Normlar ve Amaç Fonksiyonları¶
Kendimizi aşmak istemesek de, şimdiden bu kavramların neden faydalı olduğuna dair bazı sezgiler ekleyebiliriz. Derin öğrenmede, genellikle optimizasyon (eniyileme) sorunlarını çözmeye çalışıyoruz: Gözlenen verilere atanan olasılığı en üst düzeye çıkar; tahminler ve gerçek-doğru gözlemler arasındaki mesafeyi en aza indir. Benzer öğeler arasındaki mesafe en aza indirilecek ve benzer olmayan öğeler arasındaki mesafe en üst düzeye çıkarılacak şekilde öğelere (kelimeler, ürünler veya haber makaleleri gibi) vektör ifadeleri ata. Çoğu zaman, amaç fonksiyonları, ki belki de derin öğrenme algoritmalarının (verilerin yanı sıra) en önemli bileşenleridir, normlar cinsinden ifade edilir.
2.3.11. Doğrusal Cebir Hakkında Daha Fazlası¶
Sadece bu bölümde, modern derin öğrenmenin dikkate değer bir bölümünü anlamak için ihtiyaç duyacağınız tüm doğrusal cebiri öğrettik. Doğrusal cebirde çok daha fazlası vardır ve daha fazla matematik makine öğrenmesi için yararlıdır. Örneğin, matrisler faktörlere ayrıştırılabilir ve bu ayrışmalar gerçek dünya veri kümelerinde düşük boyutlu yapıları ortaya çıkarabilir. Veri kümelerindeki yapıyı keşfetmek ve tahmin problemlerini çözmek için matris ayrıştırmalarına ve onların yüksek dereceli tensörlere genellemelerini kullanmaya odaklanan koca makine öğrenmesi alt alanları vardır. Ancak bu kitap derin öğrenmeye odaklanmaktadır. Gerçek veri kümelerinde faydalı makine öğrenmesi modelleri uygulayarak ellerinizi kirlettikten sonra daha fazla matematik öğrenmeye çok daha meyilli olacağınıza inanıyoruz. Bu nedenle, daha sonra daha fazla matematiği öne sürme hakkımızı saklı tutarken, bu bölümü burada toparlayacağız.
Doğrusal cebir hakkında daha fazla bilgi edinmek istiyorsanız, şunlardan birine başvurabilirsiniz: Doğrusal cebir işlemleri üzerine çevrimiçi ek veya diğer mükemmel kaynaklar (Kolter, 2008, Petersen et al., 2008, Strang, 1993).
2.3.12. Özet¶
Skalerler, vektörler, matrisler ve tensörler doğrusal cebirdeki temel matematiksel nesnelerdir.
Vektörler skaleri genelleştirir ve matrisler vektörleri genelleştirir.
Skalerler, vektörler, matrisler ve tensörler sırasıyla sıfır, bir, iki ve rastgele sayıda eksene sahiptir.
Bir tensör, belirtilen eksenler boyunca
toplam
veortalama
ile indirgenebilir.İki matrisin eleman yönlü olarak çarpılmasına Hadamard çarpımı denir. Matris çarpımından farklıdır.
Derin öğrenmede, genellikle \(L_1\) normu, \(L_2\) normu ve Frobenius normu gibi normlarla çalışırız.
Skalerler, vektörler, matrisler ve tensörler üzerinde çeşitli işlemler gerçekleştirebiliriz.
2.3.13. Alıştırmalar¶
\(\mathbf{A}\)’nın devrik bir matrisinin devrik işleminin \(\mathbf{A}\), yani \((\mathbf{A}^\top)^\top = \mathbf{A}\) olduğunu kanıtlayın.
\(\mathbf{A}\) ve \(\mathbf{B}\) matrisleri verildiğinde, devriklerin toplamının bir toplamın devriğine eşit olduğunu gösterin: \(\mathbf{A}^\top + \mathbf{B}^\top = (\mathbf{A} + \mathbf{B})^\top\).
Herhangi bir kare matris \(\mathbf{A}\) verildiğinde, \(\mathbf{A} + \mathbf{A}^\top\) her zaman simetrik midir? Neden?
Bu bölümde (2, 3, 4) şeklinde
X
tensörünü tanımladık.len(X)
çıktısı nedir?Rasgele şekilli bir tensör
X
için,len(X)
her zaman belirli birX
ekseninin uzunluğuna karşılık gelir mi? Bu eksen nedir?A / A.sum(axis=1)
komutunu çalıştırın ve ne olduğuna bakın. Sebebini analiz edebilir misiniz?Manhattan’da iki nokta arasında seyahat ederken, koordinatlar açısından, yani cadde ve sokak cinsinden, kat etmeniz gereken mesafe nedir? Çaprazlama seyahat edebilir misiniz?
(2, 3, 4) şekilli bir tensörü düşünün. 0, 1 ve 2 ekseni boyunca toplam çıktılarının şekilleri nelerdir?
3 veya daha fazla eksenli bir tensörü
linalg.norm
fonksiyonuna besleyin ve çıktısını gözlemleyin. Bu işlev keyfi şekilli tansörler için ne hesaplar?