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