PyTorch中view()與?reshape()的區(qū)別詳析
前言
總之,兩者都是用來重塑tensor的shape的。view只適合對(duì)滿足連續(xù)性條件(contiguous)的tensor進(jìn)行操作,而reshape同時(shí)還可以對(duì)不滿足連續(xù)性條件的tensor進(jìn)行操作,具有更好的魯棒性。view能干的reshape都能干,如果view不能干就可以用reshape來處理。別看目錄挺多,但內(nèi)容很細(xì)呀~其實(shí)原理并不難啦~我們開始吧~
一、PyTorch中tensor的存儲(chǔ)方式
想要深入理解view與reshape的區(qū)別,首先要理解一些有關(guān)PyTorch張量存儲(chǔ)的底層原理,比如tensor的頭信息區(qū)(Tensor)和存儲(chǔ)區(qū) (Storage)以及tensor的步長Stride。不用慌,這部分的原理其實(shí)很簡(jiǎn)單的(^-^)!
1、PyTorch張量存儲(chǔ)的底層原理
tensor數(shù)據(jù)采用頭信息區(qū)(Tensor)和存儲(chǔ)區(qū) (Storage)分開存儲(chǔ)的形式,如圖1所示。變量名以及其存儲(chǔ)的數(shù)據(jù)是分為兩個(gè)區(qū)域分別存儲(chǔ)的。比如,我們定義并初始化一個(gè)tensor,tensor名為A,A的形狀size、步長stride、數(shù)據(jù)的索引等信息都存儲(chǔ)在頭信息區(qū),而A所存儲(chǔ)的真實(shí)數(shù)據(jù)則存儲(chǔ)在存儲(chǔ)區(qū)。另外,如果我們對(duì)A進(jìn)行截取、轉(zhuǎn)置或修改等操作后賦值給B,則B的數(shù)據(jù)共享A的存儲(chǔ)區(qū),存儲(chǔ)區(qū)的數(shù)據(jù)數(shù)量沒變,變化的只是B的頭信息區(qū)對(duì)數(shù)據(jù)的索引方式。如果聽說過淺拷貝和深拷貝的話,很容易明白這種方式其實(shí)就是淺拷貝。
圖1 Torch中Tensor的存儲(chǔ)結(jié)構(gòu)
舉個(gè)例子:
import torch a = torch.arange(5) # 初始化張量 a 為 [0, 1, 2, 3, 4] b = a[2:] # 截取張量a的部分值并賦值給b,b其實(shí)只是改變了a對(duì)數(shù)據(jù)的索引方式 print('a:', a) print('b:', b) print('ptr of storage of a:', a.storage().data_ptr()) # 打印a的存儲(chǔ)區(qū)地址 print('ptr of storage of b:', b.storage().data_ptr()) # 打印b的存儲(chǔ)區(qū)地址,可以發(fā)現(xiàn)兩者是共用存儲(chǔ)區(qū) print('==================================================================') b[1] = 0 # 修改b中索引為1,即a中索引為3的數(shù)據(jù)為0 print('a:', a) print('b:', b) print('ptr of storage of a:', a.storage().data_ptr()) # 打印a的存儲(chǔ)區(qū)地址,可以發(fā)現(xiàn)a的相應(yīng)位置的值也跟著改變,說明兩者是共用存儲(chǔ)區(qū) print('ptr of storage of b:', b.storage().data_ptr()) # 打印b的存儲(chǔ)區(qū)地址 ''' 運(yùn)行結(jié)果 ''' a: tensor([0, 1, 2, 3, 4]) b: tensor([2, 3, 4]) ptr of storage of a: 2862826251264 ptr of storage of b: 2862826251264 ================================================================== a: tensor([0, 1, 2, 0, 4]) b: tensor([2, 0, 4]) ptr of storage of a: 2862826251264 ptr of storage of b: 2862826251264
2、PyTorch張量的步長(stride)屬性
torch的tensor也是有步長屬性的,說起stride屬性是不是很耳熟?是的,卷積神經(jīng)網(wǎng)絡(luò)中卷積核對(duì)特征圖的卷積操作也是有stride屬性的,但這兩個(gè)stride可完全不是一個(gè)意思哦。tensor的步長可以理解為從索引中的一個(gè)維度跨到下一個(gè)維度中間的跨度。為方便理解,就直接用圖1說明了,您細(xì)細(xì)品(^-^):
圖2 對(duì)張量的stride屬性的理解
舉個(gè)例子:
import torch a = torch.arange(6).reshape(2, 3) # 初始化張量 a b = torch.arange(6).view(3, 2) # 初始化張量 b print('a:', a) print('stride of a:', a.stride()) # 打印a的stride print('b:', b) print('stride of b:', b.stride()) # 打印b的stride ''' 運(yùn)行結(jié)果 ''' a: tensor([[0, 1, 2], [3, 4, 5]]) stride of a: (3, 1) b: tensor([[0, 1], [2, 3], [4, 5]]) stride of b: (2, 1)
二、對(duì)“視圖(view)”字眼的理解
視圖是數(shù)據(jù)的一個(gè)別稱或引用,通過該別稱或引用亦便可訪問、操作原有數(shù)據(jù),但原有數(shù)據(jù)不會(huì)產(chǎn)生拷貝。如果我們對(duì)視圖進(jìn)行修改,它會(huì)影響到原始數(shù)據(jù),物理內(nèi)存在同一位置,這樣避免了重新創(chuàng)建張量的高內(nèi)存開銷。由上面介紹的PyTorch的張量存儲(chǔ)方式可以理解為:對(duì)張量的大部分操作就是視圖操作!
與之對(duì)應(yīng)的概念就是副本。副本是一個(gè)數(shù)據(jù)的完整的拷貝,如果我們對(duì)副本進(jìn)行修改,它不會(huì)影響到原始數(shù)據(jù),物理內(nèi)存不在同一位置。
有關(guān)視圖與副本,在NumPy中也有著重要的應(yīng)用。可參考這里。
三、view() 和reshape() 的比較
1、對(duì) torch.Tensor.view() 的理解
定義:
view(*shape) → Tensor
作用:類似于reshape,將tensor轉(zhuǎn)換為指定的shape,原始的data不改變。返回的tensor與原始的tensor共享存儲(chǔ)區(qū)。返回的tensor的size和stride必須與原始的tensor兼容。每個(gè)新的tensor的維度必須是原始維度的子空間,或滿足以下連續(xù)條件:
式1 張量連續(xù)性條件
否則需要先使用contiguous()方法將原始tensor轉(zhuǎn)換為滿足連續(xù)條件的tensor,然后就可以使用view方法進(jìn)行shape變換了?;蛘咧苯邮褂胷eshape方法進(jìn)行維度變換,但這種方法變換后的tensor就不是與原始tensor共享內(nèi)存了,而是被重新開辟了一個(gè)空間。
如何理解tensor是否滿足連續(xù)條件吶?下面通過一系列例子來慢慢理解下:
首先,我們初始化一個(gè)張量 a ,并查看其stride、storage等屬性:
import torch a = torch.arange(9).reshape(3, 3) # 初始化張量a print('struct of a:\n', a) print('size of a:', a.size()) # 查看a的shape print('stride of a:', a.stride()) # 查看a的stride ''' 運(yùn)行結(jié)果 ''' struct of a: tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) size of a: torch.Size([3, 3]) stride of a: (3, 1) # 注:滿足連續(xù)性條件
把上面的結(jié)果帶入式1,可以發(fā)現(xiàn)滿足tensor連續(xù)性條件。
我們?cè)倏催M(jìn)一步處理——對(duì)a進(jìn)行轉(zhuǎn)置后的結(jié)果:
import torch a = torch.arange(9).reshape(3, 3) # 初始化張量a b = a.permute(1, 0) # 對(duì)a進(jìn)行轉(zhuǎn)置 print('struct of b:\n', b) print('size of b:', b.size()) # 查看b的shape print('stride of b:', b.stride()) # 查看b的stride ''' 運(yùn)行結(jié)果 ''' struct of b: tensor([[0, 3, 6], [1, 4, 7], [2, 5, 8]]) size of b: torch.Size([3, 3]) stride of b: (1, 3) # 注:此時(shí)不滿足連續(xù)性條件
將a轉(zhuǎn)置后再看最后的輸出結(jié)果,帶入到式1中,是不是發(fā)現(xiàn)等式不成立了?所以此時(shí)就不滿足tensor連續(xù)的條件了。這是為什么那?我們接著往下看:
首先,輸出a和b的存儲(chǔ)區(qū)來看一下有沒有什么不同:
import torch a = torch.arange(9).reshape(3, 3) # 初始化張量a print('ptr of storage of a: ', a.storage().data_ptr()) # 查看a的storage區(qū)的地址 print('storage of a: \n', a.storage()) # 查看a的storage區(qū)的數(shù)據(jù)存放形式 b = a.permute(1, 0) # 轉(zhuǎn)置 print('ptr of storage of b: ', b.storage().data_ptr()) # 查看b的storage區(qū)的地址 print('storage of b: \n', b.storage()) # 查看b的storage區(qū)的數(shù)據(jù)存放形式 ''' 運(yùn)行結(jié)果 ''' ptr of storage of a: 2767173747136 storage of a: 0 1 2 3 4 5 6 7 8 [torch.LongStorage of size 9] ptr of storage of b: 2767173747136 storage of b: 0 1 2 3 4 5 6 7 8 [torch.LongStorage of size 9]
由結(jié)果可以看出,張量a、b仍然共用存儲(chǔ)區(qū),并且存儲(chǔ)區(qū)數(shù)據(jù)存放的順序沒有變化,這也充分說明了b與a共用存儲(chǔ)區(qū),b只是改變了數(shù)據(jù)的索引方式。那么為什么b就不符合連續(xù)性條件了吶(T-T)?其實(shí)原因很簡(jiǎn)單,我們結(jié)合圖3來解釋下:
圖3 對(duì)張量連續(xù)性條件的理解
轉(zhuǎn)置后的tensor只是對(duì)storage區(qū)數(shù)據(jù)索引方式的重映射,但原始的存放方式并沒有變化.因此,這時(shí)再看tensor b的stride,從b第一行的元素1到第二行的元素2,顯然在索引方式上已經(jīng)不是原來+1了,而是變成了新的+3了,你在仔細(xì)琢磨琢磨是不是這樣的(^-^)。所以這時(shí)候就不能用view來對(duì)b進(jìn)行shape的改變了,不然就報(bào)錯(cuò)咯,不信你看下面;
import torch a = torch.arange(9).reshape(3, 3) # 初始化張量a print(a.view(9)) print('============================================') b = a.permute(1, 0) # 轉(zhuǎn)置 print(b.view(9)) ''' 運(yùn)行結(jié)果 ''' tensor([0, 1, 2, 3, 4, 5, 6, 7, 8]) ============================================ Traceback (most recent call last): File "此處打碼", line 23, in <module> print(b.view(9)) 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.
但是嘛,上有政策下有對(duì)策,這種情況下,直接用view不行,那我就先用contiguous()方法將原始tensor轉(zhuǎn)換為滿足連續(xù)條件的tensor,在使用view進(jìn)行shape變換,值得注意的是,這樣的原理是contiguous()方法開辟了一個(gè)新的存儲(chǔ)區(qū)給b,并改變了b原始存儲(chǔ)區(qū)數(shù)據(jù)的存放順序!同樣的例子:
import torch a = torch.arange(9).reshape(3, 3) # 初始化張量a print('storage of a:\n', a.storage()) # 查看a的stride print('+++++++++++++++++++++++++++++++++++++++++++++++++') b = a.permute(1, 0).contiguous() # 轉(zhuǎn)置,并轉(zhuǎn)換為符合連續(xù)性條件的tensor print('size of b:', b.size()) # 查看b的shape print('stride of b:', b.stride()) # 查看b的stride print('viewd b:\n', b.view(9)) # 對(duì)b進(jìn)行view操作,并打印結(jié)果 print('+++++++++++++++++++++++++++++++++++++++++++++++++') print('storage of a:\n', a.storage()) # 查看a的存儲(chǔ)空間 print('storage of b:\n', b.storage()) # 查看b的存儲(chǔ)空間 print('+++++++++++++++++++++++++++++++++++++++++++++++++') print('ptr of a:\n', a.storage().data_ptr()) # 查看a的存儲(chǔ)空間地址 print('ptr of b:\n', b.storage().data_ptr()) # 查看b的存儲(chǔ)空間地址 ''' 運(yùn)行結(jié)果 ''' storage of a: 0 1 2 3 4 5 6 7 8 [torch.LongStorage of size 9] +++++++++++++++++++++++++++++++++++++++++++++++++ size of b: torch.Size([3, 3]) stride of b: (3, 1) viewd b: tensor([0, 3, 6, 1, 4, 7, 2, 5, 8]) +++++++++++++++++++++++++++++++++++++++++++++++++ storage of a: 0 1 2 3 4 5 6 7 8 [torch.LongStorage of size 9] storage of b: 0 3 6 1 4 7 2 5 8 [torch.LongStorage of size 9] +++++++++++++++++++++++++++++++++++++++++++++++++ ptr of a: 1842671472000 ptr of b: 1842671472128
由上述結(jié)果可以看出,張量a與b已經(jīng)是兩個(gè)存在于不同存儲(chǔ)區(qū)的張量了。也印證了contiguous()方法開辟了一個(gè)新的存儲(chǔ)區(qū)給b,并改變了b原始存儲(chǔ)區(qū)數(shù)據(jù)的存放順序。對(duì)應(yīng)文章開頭提到的淺拷貝,這種開辟一個(gè)新的內(nèi)存區(qū)的方式其實(shí)就是深拷貝。
2、對(duì) torch.reshape() 的理解
定義:
torch.reshape(input, shape) → Tensor
作用:與view方法類似,將輸入tensor轉(zhuǎn)換為新的shape格式。
但是reshape方法更強(qiáng)大,可以認(rèn)為a.reshape = a.view() + a.contiguous().view()。
即:在滿足tensor連續(xù)性條件時(shí),a.reshape返回的結(jié)果與a.view()相同,否則返回的結(jié)果與a.contiguous().view()相同。
不信你就看人家官方的解釋嘛,您在細(xì)細(xì)品:
關(guān)于兩者區(qū)別,還可以參考這個(gè)鏈接:
What's the difference between reshape and view in pytorch? - Stack Overflow
2021.03.30更新:最近又發(fā)現(xiàn)了pytorch官網(wǎng)對(duì)view及reshape原理的闡述,說的很清晰明了,大家可以參考下:
Tensor Views — PyTorch 1.9.0 documentation
放一張?jiān)摼W(wǎng)站上的截圖:
四、總結(jié)
torch的view()與reshape()方法都可以用來重塑tensor的shape,區(qū)別就是使用的條件不一樣。view()方法只適用于滿足連續(xù)性條件的tensor,并且該操作不會(huì)開辟新的內(nèi)存空間,只是產(chǎn)生了對(duì)原存儲(chǔ)空間的一個(gè)新別稱和引用,返回值是視圖。而reshape()方法的返回值既可以是視圖,也可以是副本,當(dāng)滿足連續(xù)性條件時(shí)返回view,否則返回副本[ 此時(shí)等價(jià)于先調(diào)用contiguous()方法在使用view() ]。因此當(dāng)不確能否使用view時(shí),可以使用reshape。如果只是想簡(jiǎn)單地重塑一個(gè)tensor的shape,那么就是用reshape,但是如果需要考慮內(nèi)存的開銷而且要確保重塑后的tensor與之前的tensor共享存儲(chǔ)空間,那就使用view()。
2020.10.23
以上是我個(gè)人看了官網(wǎng)的的解釋并實(shí)驗(yàn)得到的結(jié)論,所以有沒有dalao知道為啥沒把view廢除那?是不是還有我不知道的地方
2020.11.14
為什么沒把view廢除那?最近偶然看到了些資料,又想起了這個(gè)問題,覺得有以下原因:
1、在PyTorch不同版本的更新過程中,view先于reshape方法出現(xiàn),后來出現(xiàn)了魯棒性更好的reshape方法,但view方法并沒因此廢除。其實(shí)不止PyTorch,其他一些框架或語言比如OpenCV也有類似的操作。
2、view的存在可以顯示地表示對(duì)這個(gè)tensor的操作只能是視圖操作而非拷貝操作。這對(duì)于代碼的可讀性以及后續(xù)可能的bug的查找比較友好。
總之,我們沒必要糾結(jié)為啥a能干的b也能干,b還能做a不能干的,a存在還有啥意義的問題。就相當(dāng)于馬云能日賺1個(gè)億而我不能,那我存在的意義是啥。。。存在不就是意義嗎?存在即合理,最重要的是我們使用不同的方法可以不同程度上提升效率,何樂而不為?
到此這篇關(guān)于PyTorch中view() 與 reshape()區(qū)別的文章就介紹到這了,更多相關(guān)PyTorch view() 與 reshape() 區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談tensorflow與pytorch的相互轉(zhuǎn)換
本文主要介紹了簡(jiǎn)單介紹一下tensorflow與pytorch的相互轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06使用python畫出邏輯斯蒂映射(logistic map)中的分叉圖案例
這篇文章主要介紹了使用python畫出邏輯斯蒂映射(logistic map)中的分叉圖案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12python過濾中英文標(biāo)點(diǎn)符號(hào)的實(shí)例代碼
今天小編就為大家分享一篇python過濾中英文標(biāo)點(diǎn)符號(hào)的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-07-07jupyter notebook更換皮膚主題的實(shí)現(xiàn)
這篇文章主要介紹了jupyter notebook更換皮膚主題的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Python實(shí)現(xiàn)基于socket的udp傳輸與接收功能詳解
這篇文章主要介紹了Python實(shí)現(xiàn)基于socket的udp傳輸與接收功能,結(jié)合實(shí)例形式詳細(xì)分析了Python使用socket進(jìn)行udp文件傳輸與接收相關(guān)操作技巧及注意事項(xiàng),需要的朋友可以參考下2019-11-11使用Python實(shí)現(xiàn)windows下的抓包與解析
這篇文章主要介紹了使用Python實(shí)現(xiàn)windows下的抓包與解析,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-01-01