Lesson 2.张量的索引、分片、合并以及维度调整
張量作為有序的序列,也是具備數(shù)值索引的功能,并且基本索引方法和Python原生的列表、NumPy中的數(shù)組基本一致,當(dāng)然,所有不同的是,PyTorch中還定義了一種采用函數(shù)來進(jìn)行索引的方式。
而作為PyTorch中基本數(shù)據(jù)類型,張量即具備了列表、數(shù)組的基本功能,同時(shí)還充當(dāng)著向量、矩陣、甚至是數(shù)據(jù)框等重要數(shù)據(jù)結(jié)構(gòu),因此PyTorch中也設(shè)置了非常完備的張量合并與變換的操作。
一、張量的符號(hào)索引
張量也是有序序列,我們可以根據(jù)每個(gè)元素在系統(tǒng)內(nèi)的順序“編號(hào)”,來找出特定的元素,也就是索引。
1.一維張量索引
一維張量的索引過程和Python原生對(duì)象類型的索引一致,基本格式遵循[start: end: step],索引的基本要點(diǎn)回顧如下。
t1 = torch.arange(1, 11) t1 #tensor([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) '''從左到右,從零開始'''t1[0] #tensor(1) '''注:張量索引出來的結(jié)果還是零維張量,而不是單獨(dú)的數(shù)。要轉(zhuǎn)化成單獨(dú)的數(shù),需要使用item()方法。'''- 冒號(hào)分隔,表示對(duì)某個(gè)區(qū)域進(jìn)行索引,也就是所謂的切片
- 第二個(gè)冒號(hào),表示索引的間隔
- 冒號(hào)前后沒有值,表示索引這個(gè)區(qū)域
- 在張量的索引中,step位必須大于0
2.二維張量索引
二維張量的索引邏輯和一維張量的索引邏輯基本相同,二維張量可以視為兩個(gè)一維張量組合而成,而在實(shí)際的索引過程中,需要用逗號(hào)進(jìn)行分隔,分別表示對(duì)哪個(gè)一維張量進(jìn)行索引、以及具體的一維張量的索引。
t2 = torch.arange(1, 10).reshape(3, 3) t2 #tensor([[1, 2, 3], # [4, 5, 6], # [7, 8, 9]])t2[0, 1] '''表示索引第一行、第二個(gè)(第二列的)元素''' #tensor(2)t2[0, ::2] '''表示索引第一行、每隔兩個(gè)元素取一個(gè)''' #tensor([1, 3])t2[0, [0, 2]] '''索引結(jié)果同上''' #tensor([1, 3])t2[::2, ::2] '''表示每隔兩行取一行、并且每一行中每隔兩個(gè)元素取一個(gè)''' #tensor([[1, 3], # [7, 9]])t2[[0, 2], 1] '''索引第一行、第三行、第二列的元素''' #tensor([2, 8])理解:對(duì)二維張量來說,基本可以視為是對(duì)矩陣的索引,并且行、列的索引遵照相同的索引規(guī)范,并用逗號(hào)進(jìn)行分隔。
3.三維張量的索引
在二維張量索引的基礎(chǔ)上,三維張量擁有三個(gè)索引的維度。我們將三維張量視作矩陣組成的序列,則在實(shí)際索引過程中擁有三個(gè)維度,分別是索引矩陣、索引矩陣的行、索引矩陣的列。
t3 = torch.arange(1, 28).reshape(3, 3, 3) t3 #tensor([[[ 1, 2, 3], # [ 4, 5, 6], # [ 7, 8, 9]],# [[10, 11, 12], # [13, 14, 15], # [16, 17, 18]],# [[19, 20, 21], # [22, 23, 24], # [25, 26, 27]]])t3.shape #torch.Size([3, 3, 3])t3[1, 1, 1] '''索引第二個(gè)矩陣中,第二行、第二個(gè)元素''' #tensor(14)t3[1, ::2, ::2] '''索引第二個(gè)矩陣,行和列都是每隔兩個(gè)取一個(gè)''' #tensor([[10, 12], # [16, 18]])t3[:: 2, :: 2, :: 2] '''每隔兩個(gè)取一個(gè)矩陣,對(duì)于每個(gè)矩陣來說,行和列都是每隔兩個(gè)取一個(gè)''' #tensor([[[ 1, 3], # [ 7, 9]],# [[19, 21], # [25, 27]]])理解:更為本質(zhì)的角度去理解高維張量的索引,其實(shí)就是圍繞張量的“形狀”進(jìn)行索引
二、張量的函數(shù)索引
在PyTorch中,我們還可以使用index_select函數(shù),通過指定index來對(duì)張量進(jìn)行索引。
t1 #tensor([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])t1.ndim #1indices = torch.tensor([1, 2]) indices #tensor([1, 2])t1[1: 3] #tensor([2, 3])t1[[1, 2]] #tensor([2, 3])torch.index_select(t1, 0, indices) #tensor([2, 3])在index_select函數(shù)中,第二個(gè)參數(shù)實(shí)際上代表的是索引的維度。對(duì)于t1這個(gè)一維向量來說,由于只有一個(gè)維度,因此第二個(gè)參數(shù)取值為0,就代表在第一個(gè)維度上進(jìn)行索引
t2 = torch.arange(12).reshape(4, 3) t2 #tensor([[ 0, 1, 2], # [ 3, 4, 5], # [ 6, 7, 8], # [ 9, 10, 11]])t2.shape #torch.Size([4, 3])indices #tensor([1, 2])torch.index_select(t2, 0, indices) '''dim參數(shù)取值為0,代表在shape的第一個(gè)維度上索引''' #tensor([[3, 4, 5], # [6, 7, 8]])torch.index_select(t2, 1, indices) '''dim參數(shù)取值為1,代表在shape的第一個(gè)維度上索引''' #tensor([[ 1, 2], # [ 4, 5], # [ 7, 8], # [10, 11]])三、tensor.view()方法
在正式介紹張量的切分方法之前,需要首先介紹PyTorch中的.view()方法。該方法會(huì)返回一個(gè)類似視圖的結(jié)果,該結(jié)果和原張量對(duì)象共享一塊數(shù)據(jù)存儲(chǔ)空間,并且通過.view()方法,還可以改變對(duì)象結(jié)構(gòu),生成一個(gè)不同結(jié)構(gòu),但共享一個(gè)存儲(chǔ)空間的張量。當(dāng)然,共享一個(gè)存儲(chǔ)空間,也就代表二者是“淺拷貝”的關(guān)系,修改其中一個(gè),另一個(gè)也會(huì)同步進(jìn)行更改。
t = torch.arange(6).reshape(2, 3) t #tensor([[0, 1, 2], # [3, 4, 5]])te = t.view(3, 2) '''構(gòu)建一個(gè)數(shù)據(jù)相同,但形狀不同的“視圖”''' te #tensor([[0, 1], # [2, 3], # [4, 5]])t #tensor([[0, 1, 2], # [3, 4, 5]])t[0] = 1 '''對(duì)t進(jìn)行修改'''t #tensor([[1, 1, 1], # [3, 4, 5]])te '''te同步變化''' #tensor([[1, 1], # [1, 3], # [4, 5]])tr = t.view(1, 2, 3) # 維度也可以修改 tr #tensor([[[1, 1, 1], # [3, 4, 5]]])“視圖”的作用就是節(jié)省空間,而值得注意的是,在接下來介紹的很多切分張量的方法中,返回結(jié)果都是“視圖”,而不是新生成一個(gè)對(duì)象。
四、張量的分片函數(shù)
1.分塊:chunk函數(shù)
chunk函數(shù)能夠按照某維度,對(duì)張量進(jìn)行均勻切分,并且返回結(jié)果是原張量的視圖。
t2 = torch.arange(12).reshape(4, 3) t2 #tensor([[ 0, 1, 2], # [ 3, 4, 5], # [ 6, 7, 8], # [ 9, 10, 11]])tc = torch.chunk(t2, 4, dim=0) '''在第零個(gè)維度上(按行),進(jìn)行四等分''' tc #(tensor([[0, 1, 2]]), #tensor([[3, 4, 5]]), #tensor([[6, 7, 8]]), #tensor([[ 9, 10, 11]]))'''注意:chunk返回結(jié)果是一個(gè)視圖,不是新生成了一個(gè)對(duì)象''' tc[0] #tensor([[0, 1, 2]])tc[0][0] #tensor([0, 1, 2])tc[0][0][0] = 1 '''修改tc中的值'''tc #(tensor([[1, 1, 2]]), # tensor([[3, 4, 5]]), # tensor([[6, 7, 8]]), # tensor([[ 9, 10, 11]]))t2 '''原張量也會(huì)對(duì)應(yīng)發(fā)生變化''' #tensor([[ 1, 1, 2], # [ 3, 4, 5], # [ 6, 7, 8], # [ 9, 10, 11]])"""當(dāng)原張量不能均分時(shí),chunk不會(huì)報(bào)錯(cuò),但會(huì)返回其他均分的結(jié)果""" torch.chunk(t2, 3, dim=0) '''次一級(jí)均分結(jié)果''' #(tensor([[1, 1, 2], # [3, 4, 5]]), # tensor([[ 6, 7, 8], # [ 9, 10, 11]]))len(torch.chunk(t2, 3, dim=0)) #2torch.chunk(t2, 5, dim=0) '''次一級(jí)均分結(jié)果''' #(tensor([[1, 1, 2]]), # tensor([[3, 4, 5]]), # tensor([[6, 7, 8]]), # tensor([[ 9, 10, 11]]))2.拆分:split函數(shù)
split既能進(jìn)行均分,也能進(jìn)行自定義切分。當(dāng)然,需要注意的是,和chunk函數(shù)一樣,split返回結(jié)果也是view。
t2 = torch.arange(12).reshape(4, 3) t2 #tensor([[ 0, 1, 2], # [ 3, 4, 5], # [ 6, 7, 8], # [ 9, 10, 11]])torch.split(t2, 2, 0) '''第二個(gè)參數(shù)只輸入一個(gè)數(shù)值時(shí)表示均分,第三個(gè)參數(shù)表示切分的維度''' #(tensor([[0, 1, 2], '''按照第二個(gè)參數(shù)的數(shù)值均分''' # [3, 4, 5]]), # tensor([[ 6, 7, 8], # [ 9, 10, 11]]))torch.split(t2, [1, 3], 0) '''第二個(gè)參數(shù)輸入一個(gè)序列時(shí),表示按照序列數(shù)值進(jìn)行切分,也就是1/3分''' #(tensor([[0, 1, 2]]), # tensor([[ 3, 4, 5], # [ 6, 7, 8], # [ 9, 10, 11]]))注意,當(dāng)?shù)诙€(gè)參數(shù)位輸入一個(gè)序列時(shí),序列的各數(shù)值的和必須等于對(duì)應(yīng)維度下形狀分量的取值。例如,上述代碼中,是按照第一個(gè)維度進(jìn)行切分,而t2總共有4行,因此序列的求和必須等于4,也就是1+3=4,而序列中每個(gè)分量的取值,則代表切塊大小。
torch.split(t2, [1, 1, 1, 1], 0) #(tensor([[0, 1, 2]]), # tensor([[3, 4, 5]]), # tensor([[6, 7, 8]]), # tensor([[ 9, 10, 11]]))torch.split(t2, [1, 1, 2], 0) #(tensor([[0, 1, 2]]), # tensor([[3, 4, 5]]), # tensor([[ 6, 7, 8], # [ 9, 10, 11]]))ts = torch.split(t2, [1, 2], 1) ts #(tensor([[0], # [3], # [6], # [9]]), # tensor([[ 1, 2], # [ 4, 5], # [ 7, 8], # [10, 11]]))ts[0][0] #tensor([0])ts[0][0] = 1 '''view進(jìn)行修改''' ts #(tensor([[1], # [3], # [6], # [9]]), # tensor([[ 1, 2], # [ 4, 5], # [ 7, 8], # [10, 11]]))t2 '''原對(duì)象同步改變''' #tensor([[ 1, 1, 2], # [ 3, 4, 5], # [ 6, 7, 8], # [ 9, 10, 11]])tensor的split方法和array的split方法有很大的區(qū)別,array的split方法是根據(jù)索引進(jìn)行切分。
五、張量的合并操作
張量的合并操作類似與列表的追加元素,可以拼接、也可以堆疊。
- 拼接函數(shù):cat
PyTorch中,可以使用cat函數(shù)實(shí)現(xiàn)張量的拼接。
a = torch.zeros(2, 3) a #tensor([[0., 0., 0.], # [0., 0., 0.]])b = torch.ones(2, 3) b #tensor([[1., 1., 1.], # [1., 1., 1.]])c = torch.zeros(3, 3) c #tensor([[0., 0., 0.], # [0., 0., 0.], # [0., 0., 0.]])torch.cat([a, b]) '''按照行進(jìn)行拼接,dim默認(rèn)取值為0''' #tensor([[0., 0., 0.], # [0., 0., 0.], # [1., 1., 1.], # [1., 1., 1.]])torch.cat([a, b], 1) '''按照列進(jìn)行拼接''' #tensor([[0., 0., 0., 1., 1., 1.], # [0., 0., 0., 1., 1., 1.]])torch.cat([a, c], 1) '''形狀不匹配時(shí)將報(bào)錯(cuò)'''注意理解,拼接的本質(zhì)是實(shí)現(xiàn)元素的堆積,也就是構(gòu)成a、b兩個(gè)二維張量的各一維張量的堆積,最終還是構(gòu)成二維向量。
- 堆疊函數(shù):stack
和拼接不同,堆疊不是將元素拆分重裝,而是簡(jiǎn)單的將各參與堆疊的對(duì)象分裝到一個(gè)更高維度的張量里。
a #tensor([[0., 0., 0.], # [0., 0., 0.]])b #tensor([[1., 1., 1.], # [1., 1., 1.]])torch.stack([a, b]) #堆疊之后,生成一個(gè)三維張量 #tensor([[[0., 0., 0.], # [0., 0., 0.]],# [[1., 1., 1.], # [1., 1., 1.]]])torch.stack([a, b]).shape #torch.Size([2, 2, 3])torch.cat([a, b]) #tensor([[0., 0., 0.], # [0., 0., 0.], # [1., 1., 1.], # [1., 1., 1.]])注意對(duì)比二者區(qū)別,拼接之后維度不變,堆疊之后維度升高。拼接是把一個(gè)個(gè)元素單獨(dú)提取出來之后再放到二維張量中,而堆疊則是直接將兩個(gè)二維張量封裝到一個(gè)三維張量中,因此,堆疊的要求更高,參與堆疊的張量必須形狀完全相同。
a #tensor([[0., 0., 0.], # [0., 0., 0.]])c #tensor([[0., 0., 0.], # [0., 0., 0.], # [0., 0., 0.]])torch.cat([a, c]) '''橫向拼接時(shí),對(duì)行數(shù)沒有一致性要求''' #tensor([[0., 0., 0.], # [0., 0., 0.], # [0., 0., 0.], # [0., 0., 0.], # [0., 0., 0.]])torch.stack([a, c]) '''維度不匹配時(shí)也會(huì)報(bào)錯(cuò)'''六、張量維度變換
此前我們介紹過,通過reshape方法,能夠靈活調(diào)整張量的形狀。而在實(shí)際操作張量進(jìn)行計(jì)算時(shí),往往需要另外進(jìn)行降維和升維的操作,當(dāng)我們需要除去不必要的維度時(shí),可以使用squeeze函數(shù),而需要手動(dòng)升維時(shí),則可采用unsqueeze函數(shù)。
a = torch.arange(4) a #tensor([0, 1, 2, 3])a2 = a.reshape(1, 4) a2 #tensor([[0, 1, 2, 3]])torch.squeeze(a2).ndim #1'''squeeze函數(shù):刪除不必要的維度''' t = torch.zeros(1, 1, 3, 1) t #tensor([[[[0.], # [0.], # [0.]]]])t.shape #torch.Size([1, 1, 3, 1])'''t張量解釋:一個(gè)包含一個(gè)三維的四維張量,三維張量只包含一個(gè)三行一列的二維張量。''' torch.squeeze(t) #tensor([0., 0., 0.])torch.squeeze(t).shape #torch.Size([3]) '''轉(zhuǎn)化后生成了一個(gè)一維張量'''t1 = torch.zeros(1, 1, 3, 2, 1, 2) t1.shape #torch.Size([1, 1, 3, 2, 1, 2])torch.squeeze(t1) #tensor([[[0., 0.], # [0., 0.]],# [[0., 0.], # [0., 0.]],# [[0., 0.], # [0., 0.]]])torch.squeeze(t1).shape #torch.Size([3, 2, 2]) '''簡(jiǎn)單理解,squeeze就相當(dāng)于提出了shape返回結(jié)果中的1'''- unsqeeze函數(shù):手動(dòng)升維
注意理解維度和shape返回結(jié)果一一對(duì)應(yīng)的關(guān)系,shape返回的序列有幾個(gè)元素,張量就有多少維度。
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Lesson 2.张量的索引、分片、合并以及维度调整的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 原创:“墙倒众人推”?德云社门面相继离开
- 下一篇: 李宏毅深度学习——Backpropaga