張量分析(由淺入深地帶你了解分析張量)
神經(jīng)網(wǎng)絡(luò)的輸入、輸出、權(quán)重都是張量,神經(jīng)網(wǎng)絡(luò)中的各種計(jì)算和變換就是對(duì)張量操作,張量這種數(shù)據(jù)結(jié)構(gòu)是神經(jīng)網(wǎng)絡(luò)的基石,可以說(shuō)沒(méi)有理解張量就沒(méi)有真正理解神經(jīng)網(wǎng)絡(luò)和人工智能。本文由淺入深地詳細(xì)講解分析張量,望能給予讀者啟發(fā)——袁宵。
張量的定義
張量(tensor)是一個(gè)多維數(shù)組(multidimensional arrays),即一種存儲(chǔ)數(shù)字集合的數(shù)據(jù)結(jié)構(gòu),這些數(shù)字可通過(guò)索引(index)單獨(dú)訪問(wèn),并可通過(guò)多個(gè)索引進(jìn)行索引。
張量是將向量和矩陣推廣到任意維數(shù)。如下圖所示,一個(gè)張量的維數(shù)與張量中用來(lái)表示標(biāo)量值的索引的數(shù)量一致。
新張量 = 張量[索引]
張量的視圖與存儲(chǔ)
點(diǎn)擊張量的存儲(chǔ).ipynb 深入學(xué)習(xí),下面是該文件的主要內(nèi)容:
張量,PyTorch中的基本數(shù)據(jù)結(jié)構(gòu)
索引并在PyTorch張量上進(jìn)行操作以探索和處理數(shù)據(jù)
與NumPy多維數(shù)組互操作
將計(jì)算移至GPU以提高速度
張量的視圖與存儲(chǔ)的定義
存儲(chǔ)(Storage)是一維的數(shù)字?jǐn)?shù)據(jù)數(shù)組,例如包含給定類型的數(shù)字(可能是float或int32)的連續(xù)內(nèi)存塊。張量是這樣一個(gè)存儲(chǔ)的視圖,它能夠通過(guò)使用偏移量(offset)和每一維度的步長(zhǎng)(per-dimension strides)索引(index)到該存儲(chǔ)中。存儲(chǔ)的布局總是一維的,而與可能涉及到它的任何張量的維數(shù)無(wú)關(guān)。
多個(gè)張量可以對(duì)相同的存儲(chǔ)進(jìn)行索引,即使它們對(duì)數(shù)據(jù)的索引是不同的。但是,底層內(nèi)存只分配一次,因此不管存儲(chǔ)實(shí)例管理的數(shù)據(jù)有多大,都可以快速地創(chuàng)建數(shù)據(jù)上的替代張量視圖。
張量視圖的多維性意義
張量的視圖就是我們理解張量的方式,比如 shape 為[2,4,4,3]的張量 A,我們從邏輯上可以理解 為 2 張圖片,每張圖片 4 行 4 列,每個(gè)位置有 RGB 3 個(gè)通道的數(shù)據(jù);張量的存儲(chǔ)體現(xiàn)在張 量在內(nèi)存上保存為一段連續(xù)的內(nèi)存區(qū)域,對(duì)于同樣的存儲(chǔ),我們可以有不同的理解方式, 比如上述 A,我們可以在不改變張量的存儲(chǔ)下,將張量 A 理解為 2 個(gè)樣本,每個(gè)樣本的特征為長(zhǎng)度 48 的向量。這就是存儲(chǔ)與視圖的關(guān)系。
張量存儲(chǔ)的一維性
在存儲(chǔ)數(shù)據(jù)時(shí),內(nèi)存并不支持這個(gè)維度層級(jí)概念,只能以平鋪方式按序?qū)懭雰?nèi)存,因此這 種層級(jí)關(guān)系需要人為管理,也就是說(shuō),每個(gè)張量的存儲(chǔ)順序需要人為跟蹤。為了方便表達(dá),我們把張量 shape 中相對(duì)靠左側(cè)的維度叫做大維度,shape 中相對(duì)靠右側(cè)的維度叫做小維度,比如[2,4,4,3]的張量中,圖片數(shù)量維度與通道數(shù)量相比,圖片數(shù)量叫做大維度,通道 數(shù)叫做小維度。在優(yōu)先寫(xiě)入小維度的設(shè)定下,形狀(2, 3)張量的內(nèi)存布局為:
<Tensor: shape=(3, 2), dtype=float32, numpy= array([[1., 4.], [2., 1.], [3., 5.]], dtype=float32)> [1., 4., 2., 1., 3., 5.]
數(shù)據(jù)在創(chuàng)建時(shí)按著初始的維度順序?qū)懭耄淖儚埩康囊晥D僅僅是改變了張量的理解方 式,并不會(huì)改變張量的存儲(chǔ)順序,這在一定程度上是從計(jì)算效率考慮的,大量數(shù)據(jù)的寫(xiě)入 操作會(huì)消耗較多的計(jì)算資源。
張量存儲(chǔ)的形狀(大小)、存儲(chǔ)偏移量和步長(zhǎng)
為了索引到存儲(chǔ)中,張量依賴于一些信息,這些信息連同它們的存儲(chǔ)一起明確地定義了它們:大小、存儲(chǔ)偏移量和步長(zhǎng)(下圖)。
中文英文意義形狀shape是一個(gè)元組,表示張量表示的每個(gè)維度上有多少個(gè)元素。注意張量的形狀(shape)與存儲(chǔ)的大小(size)等價(jià)。步長(zhǎng)stride是一個(gè)元組,表示當(dāng)索引在每個(gè)維度上增加1時(shí),必須跳過(guò)的存儲(chǔ)中的元素?cái)?shù)量。存儲(chǔ)偏移量storage offset存儲(chǔ)中對(duì)應(yīng)于張量中第一個(gè)元素的index。
上圖例子中,在二維張量中訪問(wèn)元素(i,j)(i,j)的結(jié)果是訪問(wèn)存儲(chǔ)中的$storage_offset + stride[0] i + stride[1] j$元素。
更加廣義的:對(duì)于形狀為shape(d1,d2,..,dn)shape(d1,d2,..,dn)的張量的視圖中的元素E(e1,e2,…,en)E(e1,e2,…,en),如果該張量的存儲(chǔ)的步長(zhǎng)為 stride(s1,s2,…,sn)stride(s1,s2,…,sn) 、存儲(chǔ)偏移量為 storage offsetstorage offset,那么元素EE的存儲(chǔ)位置indexindex是:
由此我們得出了張量視圖的計(jì)算式子:
張量視圖 = 張量存儲(chǔ) + 張量形狀 + 張量步長(zhǎng) + 張量偏移
張量存儲(chǔ)對(duì)張量操作的影響
這種張量和存儲(chǔ)之間的間接性導(dǎo)致了一些操作,比如轉(zhuǎn)置一個(gè)張量或者提取一個(gè)次張量,這些操作是便宜的,因?yàn)樗鼈儾粫?huì)導(dǎo)致內(nèi)存的重新分配;而是,它們包括分配一個(gè)新的張量對(duì)象,這個(gè)張量對(duì)象的形狀、存儲(chǔ)偏移量或步長(zhǎng)有不同的值。
子張量的維數(shù)變少,而索引的存儲(chǔ)空間仍然和原來(lái)的點(diǎn)張量一樣。改變子張量會(huì)對(duì)原張量產(chǎn)生副作用(對(duì)子張量的修改會(huì)影響原張量)。但是這種效果可能并不總是存在,因?yàn)榭梢园炎訌埩靠寺〕梢粋€(gè)新的張量。
沒(méi)有分配新的內(nèi)存:只有通過(guò)創(chuàng)建一個(gè)新的張量實(shí)例來(lái)獲得轉(zhuǎn)置(transpose),這個(gè)張量實(shí)例的步長(zhǎng)與原來(lái)的張量不同。可以通過(guò)張量的重新布局函數(shù),比如PyTorch中的contiguous()函數(shù),來(lái)強(qiáng)制拷貝一份張量,讓它的布局和從新創(chuàng)建的張量一樣。
張量的視圖與存儲(chǔ)的區(qū)別與聯(lián)系
聯(lián)系
對(duì)于形狀 shape 為(d1, d2,.., dn)的張量的視圖中的元素E(e1, e2,…,en),如果該張量的存儲(chǔ)的步長(zhǎng)為 stride 為 (s1, s2,…,sn) 、存儲(chǔ)偏移量storage offset 為 s_o,那么元素E的存儲(chǔ)位置index是:
張量視圖 = 張量存儲(chǔ) + 張量形狀 + 張量步長(zhǎng) + 張量偏移
區(qū)別
相同存儲(chǔ)可以有不同的視圖:tensor_B.storage() 與 tensor_B_transpose.storage() 相同,但是 tensor_B 與 tensor_B_transpose 不同。
相同的視圖可以有不同的存儲(chǔ):tensor_A 與 tensor_B_transpose 相同,但是 tensor_A.storage() 與 tensor_B_transpose.storage() 不同。
總結(jié):張量的視圖與存儲(chǔ)通過(guò)索引來(lái)建立關(guān)系,它們之間沒(méi)有必然性,即相同存儲(chǔ)可以有不同的視圖,相同的視圖可以有不同的存儲(chǔ)。
張量的操作
點(diǎn)擊 TensorFlow張量的常用操作.ipynb 深入學(xué)習(xí),下面是該文件的主要內(nèi)容:
dtype=int32, float32, string, bool tf.convert_to_tensor, tf.constant, tf.zeros, tf.ones, tf.zeros_like, tf.fill, tf.random.normal, tf.random.uniform, tf.range A[1][2][1], A[1, 2, 1], A[ :, :, 0:3:2], A[..., 0:3:2] tf.reshape, tf.expand_dims, tf.squeeze, tf.transpose tf.tile +, -, *, /, //, %, **, tf.pow, tf.square, tf.sqrt, tf.math.log, tf.matmul, @ tf.concat, tf.stack, tf.split, tf.unstack tf.norm, tf.reduce_max min mean sum, tf.argmax, tf.argmin tf.equal tf.pad, tf.keras.preprocessing.sequence.pad_sequences, tf.tile tf.maximum, tf.minimum, tf.clip_by_value tf.gather, tf.gather_nd tf.boolean_mask tf.where tf.scatter_nd tf.meshgrid