午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

PyTorch:view() 與 reshape() 區(qū)別詳解

 LibraryPKU 2021-03-17

總之,兩者都是用來重塑tensor的shape的。view只適合對滿足連續(xù)性條件(contiguous)的tensor進行操作,而reshape同時還可以對不滿足連續(xù)性條件的tensor進行操作,具有更好的魯棒性。view能干的reshape都能干,如果view不能干就可以用reshape來處理。別看目錄挺多,但內(nèi)容很細(xì)呀~其實原理并不難啦~我們開始吧~

目錄

一、PyTorch中tensor的存儲方式

1、PyTorch張量存儲的底層原理

2、PyTorch張量的步長(stride)屬性

二、對“視圖(view)”字眼的理解

三、view() 和reshape() 的比較

1、對 torch.Tensor.view() 的理解

2、對 torch.reshape() 的理解

四、總結(jié)


一、PyTorch中tensor的存儲方式

想要深入理解view與reshape的區(qū)別,首先要理解一些有關(guān)PyTorch張量存儲的底層原理,比如tensor的頭信息區(qū)(Tensor)和存儲區(qū) (Storage)以及tensor的步長Stride。不用慌,這部分的原理其實很簡單的(^-^)!

1、PyTorch張量存儲的底層原理

tensor數(shù)據(jù)采用頭信息區(qū)(Tensor)和存儲區(qū) (Storage)分開存儲的形式,如圖1所示。變量名以及其存儲的數(shù)據(jù)是分為兩個區(qū)域分別存儲的。比如,我們定義并初始化一個tensor,tensor名為A,A的形狀size、步長stride、數(shù)據(jù)的索引等信息都存儲在頭信息區(qū),而A所存儲的真實數(shù)據(jù)則存儲在存儲區(qū)。另外,如果我們對A進行截取、轉(zhuǎn)置或修改等操作后賦值給B,則B的數(shù)據(jù)共享A的存儲區(qū),存儲區(qū)的數(shù)據(jù)數(shù)量沒變,變化的只是B的頭信息區(qū)對數(shù)據(jù)的索引方式。

圖1 Torch中Tensor的存儲結(jié)構(gòu)

舉個例子:

  1. import torch
  2. a = torch.arange(5) # 初始化張量 a 為 [0, 1, 2, 3, 4]
  3. b = a[2:] # 截取張量a的部分值并賦值給b,b其實只是改變了a對數(shù)據(jù)的索引方式
  4. print('a:', a)
  5. print('b:', b)
  6. print('id of storage of a:', id(a.storage)) # 打印a的存儲區(qū)地址
  7. print('id of storage of b:', id(b.storage)) # 打印b的存儲區(qū)地址,可以發(fā)現(xiàn)兩者是共用存儲區(qū)

  8. print('==================================================================')

  9. b[1] = 0 # 修改b中索引為1,即a中索引為3的數(shù)據(jù)為0
  10. print('a:', a)
  11. print('b:', b)
  12. print('id of storage of a:', id(a.storage)) # 打印a的存儲區(qū)地址,可以發(fā)現(xiàn)a的相應(yīng)位置的值也跟著改變,說明兩者是共用存儲區(qū)
  13. print('id of storage of b:', id(b.storage)) # 打印b的存儲區(qū)地址


  14. ''' 運行結(jié)果 '''
  15. a: tensor([0, 1, 2, 3, 4])
  16. b: tensor([2, 3, 4])
  17. id of storage of a: 140424434241200
  18. id of storage of b: 140424434241200
  19. ==================================================================
  20. a: tensor([0, 1, 2, 0, 4])
  21. b: tensor([2, 0, 4])
  22. id of storage of a: 140424434241200
  23. id of storage of b: 140424434241200

2、PyTorch張量的步長(stride)屬性

torch的tensor也是有步長屬性的,說起stride屬性是不是很耳熟?是的,卷積神經(jīng)網(wǎng)絡(luò)中卷積核對特征圖的卷積操作也是有stride屬性的,但這兩個stride可完全不是一個意思哦。tensor的步長可以理解為從索引中的一個維度跨到下一個維度中間的跨度。為方便理解,就直接用圖1說明了,您細(xì)細(xì)品(^-^):

圖2 對張量的stride屬性的理解

舉個例子:

  1. import torch
  2. a = torch.arange(6).reshape(2, 3) # 初始化張量 a
  3. b = torch.arange(6).view(3, 2) # 初始化張量 b
  4. print('a:', a)
  5. print('stride of a:', a.stride()) # 打印a的stride
  6. print('b:', b)
  7. print('stride of b:', b.stride()) # 打印b的stride

  8. ''' 運行結(jié)果 '''
  9. a: tensor([[0, 1, 2],
  10. [3, 4, 5]])
  11. stride of a: (3, 1)

  12. b: tensor([[0, 1],
  13. [2, 3],
  14. [4, 5]])
  15. stride of b: (2, 1)

二、對“視圖(view)”字眼的理解

視圖是數(shù)據(jù)的一個別稱或引用,通過該別稱或引用亦便可訪問、操作原有數(shù)據(jù),但原有數(shù)據(jù)不會產(chǎn)生拷貝。如果我們對視圖進行修改,它會影響到原始數(shù)據(jù),物理內(nèi)存在同一位置,這樣避免了重新創(chuàng)建張量的高內(nèi)存開銷。由上面介紹的PyTorch的張量存儲方式可以理解為:對張量的大部分操作就是視圖操作!

與之對應(yīng)的概念就是副本。副本是一個數(shù)據(jù)的完整的拷貝,如果我們對副本進行修改,它不會影響到原始數(shù)據(jù),物理內(nèi)存不在同一位置。

有關(guān)視圖與副本,在NumPy中也有著重要的應(yīng)用。可參考這里。

三、view() 和reshape() 的比較

1、對 torch.Tensor.view() 的理解

定義:

view(*shape) → Tensor

作用:類似于reshape,將tensor轉(zhuǎn)換為指定的shape,原始的data不改變。返回的tensor與原始的tensor共享存儲區(qū)。返回的tensor的size和stride必須與原始的tensor兼容。每個新的tensor的維度必須是原始維度的子空間,或滿足以下連續(xù)條件:

式1 張量連續(xù)性條件

否則需要先使用contiguous()方法將原始tensor轉(zhuǎn)換為滿足連續(xù)條件的tensor,然后就可以使用view方法進行shape變換了?;蛘咧苯邮褂胷eshape方法進行維度變換,但這種方法變換后的tensor就不是與原始tensor共享內(nèi)存了,而是被重新開辟了一個空間。

如何理解tensor是否滿足連續(xù)條件吶?下面通過一系列例子來慢慢理解下:

首先,我們初始化一個張量 a ,并查看其stride、storage等屬性:

  1. import torch
  2. a = torch.arange(9).reshape(3, 3) # 初始化張量a
  3. print('struct of a:\n', a)
  4. print('size of a:', a.size()) # 查看a的shape
  5. print('stride of a:', a.stride()) # 查看a的stride

  6. ''' 運行結(jié)果 '''
  7. struct of a:
  8. tensor([[0, 1, 2],
  9. [3, 4, 5],
  10. [6, 7, 8]])
  11. size of a: torch.Size([3, 3])
  12. stride of a: (3, 1) # 注:滿足連續(xù)性條件

把上面的結(jié)果帶入式1,可以發(fā)現(xiàn)滿足tensor連續(xù)性條件。

我們再看進一步處理——對a進行轉(zhuǎn)置后的結(jié)果:

  1. import torch
  2. a = torch.arange(9).reshape(3, 3) # 初始化張量a
  3. b = a.permute(1, 0) # 對a進行轉(zhuǎn)置
  4. print('struct of b:\n', b)
  5. print('size of b:', b.size()) # 查看b的shape
  6. print('stride of b:', b.stride()) # 查看b的stride

  7. ''' 運行結(jié)果 '''
  8. struct of b:
  9. tensor([[0, 3, 6],
  10. [1, 4, 7],
  11. [2, 5, 8]])
  12. size of b: torch.Size([3, 3])
  13. stride of b: (1, 3) # 注:此時不滿足連續(xù)性條件

 將a轉(zhuǎn)置后再看最后的輸出結(jié)果,帶入到式1中,是不是發(fā)現(xiàn)等式不成立了?所以此時就不滿足tensor連續(xù)的條件了。這是為什么那?我們接著往下看:

首先,輸出a和b的存儲區(qū)來看一下有沒有什么不同:

  1. import torch
  2. a = torch.arange(9).reshape(3, 3) # 初始化張量a
  3. print('id of storage of a: ', id(a.storage)) # 查看a的storage區(qū)的地址
  4. print('storage of a: \n', a.storage()) # 查看a的storage區(qū)的數(shù)據(jù)存放形式
  5. b = a.permute(1, 0) # 轉(zhuǎn)置
  6. print('id of storage of b: ', id(b.storage)) # 查看b的storage區(qū)的地址
  7. print('storage of b: \n', b.storage()) # 查看b的storage區(qū)的數(shù)據(jù)存放形式

  8. ''' 運行結(jié)果 '''
  9. id of storage of a: 1977594687560
  10. storage of a:
  11. 0
  12. 1
  13. 2
  14. 3
  15. 4
  16. 5
  17. 6
  18. 7
  19. 8
  20. [torch.LongStorage of size 9]
  21. id of storage of b: 1977594687560
  22. storage of b:
  23. 0
  24. 1
  25. 2
  26. 3
  27. 4
  28. 5
  29. 6
  30. 7
  31. 8
  32. [torch.LongStorage of size 9]

 由結(jié)果可以看出,張量a、b仍然共用存儲區(qū),并且存儲區(qū)數(shù)據(jù)存放的順序沒有變化,這也充分說明了b與a共用存儲區(qū),b只是改變了數(shù)據(jù)的索引方式。那么為什么b就不符合連續(xù)性條件了吶(T-T)?其實原因很簡單,我們結(jié)合圖3來解釋下:

圖3 對張量連續(xù)性條件的理解

轉(zhuǎn)置后的tensor只是對storage區(qū)數(shù)據(jù)索引方式的重映射,但原始的存放方式并沒有變化.因此,這時再看tensor b的stride,從b第一行的元素1到第二行的元素2,顯然在索引方式上已經(jīng)不是原來+1了,而是變成了新的+3了,你在仔細(xì)琢磨琢磨是不是這樣的(^-^)。所以這時候就不能用view來對b進行shape的改變了,不然就報錯咯,不信你看下面;

  1. import torch
  2. a = torch.arange(9).reshape(3, 3) # 初始化張量a
  3. print(a.view(9))
  4. print('============================================')
  5. b = a.permute(1, 0) # 轉(zhuǎn)置
  6. print(b.view(9))

  7. ''' 運行結(jié)果 '''
  8. tensor([0, 1, 2, 3, 4, 5, 6, 7, 8])
  9. ============================================
  10. Traceback (most recent call last):
  11. File "此處打碼", line 23, in <module>
  12. print(b.view(9))
  13. RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.

但是嘛,上有政策下有對策,這種情況下,直接用view不行,那我就先用contiguous()方法將原始tensor轉(zhuǎn)換為滿足連續(xù)條件的tensor,在使用view進行shape變換,值得注意的是,這樣的原理是contiguous()方法開辟了一個新的存儲區(qū)給b,并改變了b原始存儲區(qū)數(shù)據(jù)的存放順序!同樣的例子:

  1. import torch
  2. a = torch.arange(9).reshape(3, 3) # 初始化張量a
  3. print('storage of a:\n', a.storage()) # 查看a的stride
  4. print('+++++++++++++++++++++++++++++++++++++++++++++++++')
  5. b = a.permute(1, 0).contiguous() # 轉(zhuǎn)置,并轉(zhuǎn)換為符合連續(xù)性條件的tensor
  6. print('size of b:', b.size()) # 查看b的shape
  7. print('stride of b:', b.stride()) # 查看b的stride
  8. print('viewd b:\n', b.view(9)) # 對b進行view操作,并打印結(jié)果
  9. print('+++++++++++++++++++++++++++++++++++++++++++++++++')
  10. print('storage of a:\n', a.storage()) # 查看a的存儲空間
  11. print('storage of b:\n', b.storage()) # 查看b的存儲空間

  12. ''' 運行結(jié)果 '''
  13. storage of a:
  14. 0
  15. 1
  16. 2
  17. 3
  18. 4
  19. 5
  20. 6
  21. 7
  22. 8
  23. [torch.LongStorage of size 9]
  24. +++++++++++++++++++++++++++++++++++++++++++++++++
  25. size of b: torch.Size([3, 3]) # 注意:這時的b就滿足連續(xù)性條件了
  26. stride of b: (3, 1)
  27. viewd b:
  28. tensor([0, 3, 6, 1, 4, 7, 2, 5, 8])
  29. +++++++++++++++++++++++++++++++++++++++++++++++++
  30. storage of a: # 注意:這時b的存儲區(qū)變化了,a的還沒變
  31. 0 # 這說明contiguous方法為b另外開辟了存儲區(qū)
  32. 1 # 此時a、b不再共享存儲區(qū)
  33. 2
  34. 3
  35. 4
  36. 5
  37. 6
  38. 7
  39. 8
  40. [torch.LongStorage of size 9]
  41. storage of b:
  42. 0
  43. 3
  44. 6
  45. 1
  46. 4
  47. 7
  48. 2
  49. 5
  50. 8
  51. [torch.LongStorage of size 9]

2、對 torch.reshape() 的理解

定義:

torch.reshape(input, shape) → Tensor

作用:與view方法類似,將輸入tensor轉(zhuǎn)換為新的shape格式。

但是reshape方法更強大,可以認(rèn)為a.reshape = a.view() + a.contiguous().view()

即:在滿足tensor連續(xù)性條件時,a.reshape返回的結(jié)果與a.view()相同,否則返回的結(jié)果與a.contiguous().view()相同。

不信你就看人家官方的解釋嘛,您在細(xì)細(xì)品:

關(guān)于兩者區(qū)別,還可以參考這個鏈接:https:///questions/49643225/whats-the-difference-between-reshape-and-view-in-pytorch

四、總結(jié)

torch的view()與reshape()方法都可以用來重塑tensor的shape,區(qū)別就是使用的條件不一樣。view()方法只適用于滿足連續(xù)性條件的tensor,并且該操作不會開辟新的內(nèi)存空間,只是產(chǎn)生了對原存儲空間的一個新別稱和引用,返回值是視圖。而reshape()方法的返回值既可以是視圖,也可以是副本,當(dāng)滿足連續(xù)性條件時返回view,否則返回副本[ 此時等價于先調(diào)用contiguous()方法在使用view() ]。因此當(dāng)不確能否使用view時,可以使用reshape。如果只是想簡單地重塑一個tensor的shape,那么就是用reshape,但是如果需要考慮內(nèi)存的開銷而且要確保重塑后的tensor與之前的tensor共享存儲空間,那就使用view()。

2020.10.23

以上是我個人看了官網(wǎng)的的解釋并實驗得到的結(jié)論,所以有沒有dalao知道為啥沒把view廢除那?是不是還有我不知道的地方

2020.11.14

為什么沒把view廢除那?最近偶然看到了些資料,又想起了這個問題,覺得有以下原因:

1、在PyTorch不同版本的更新過程中,view先于reshape方法出現(xiàn),后來出現(xiàn)了魯棒性更好的reshape方法,但view方法并沒因此廢除。其實不止PyTorch,其他一些框架或語言比如OpenCV也有類似的操作。

2、view的存在可以顯示地表示對這個tensor的操作只能是視圖操作而非拷貝操作。這對于代碼的可讀性以及后續(xù)可能的bug的查找比較友好。

總之,我們沒必要糾結(jié)為啥a能干的b也能干,b還能做a不能干的,a存在還有啥意義的問題。就相當(dāng)于馬云能日賺1個億而我不能,那我存在的意義是啥。。。存在不就是意義嗎?存在即合理,最重要的是我們使用不同的方法可以不同程度上提升效率,何樂而不為?

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多