深度学习框架PyTorch一书的学习-第三章-Tensor和autograd-1-Tensor
?
參考https://github.com/chenyuntc/pytorch-book/tree/v1.0
希望大家直接到上面的網(wǎng)址去查看代碼,下面是本人的筆記
Tensor
Tensor可以是一個數(shù)(標(biāo)量)、一維數(shù)組(向量)、二維數(shù)組(矩陣)或更高維的數(shù)組(高階數(shù)據(jù))
Tensor和numpy的ndarrays類似,不同在于pytorch的tensor支持GPU加速
導(dǎo)包:
from __future__ import print_function import torch as t?
判斷是否為張量Tensor
a = t.arange(0,6).view(2,3) a返回:
tensor([[0, 1, 2],[3, 4, 5]])判斷:
t.is_tensor(a) #返回True?
如果創(chuàng)建的是一個數(shù)值
k = 1 t.is_tensor(k)#返回False??
1.基礎(chǔ)操作
從接口的角度將tensor的操作分為兩類:
1)torch.function,如torch.save()等
2)tensor.function,如tensor.view()等
對tensor的大部分操作同時支持這兩類接口,如torch.sum(a,b)等價于a.sum(b)
?
從存儲角度將tensor的操作分為兩類:
1)不會修改自身數(shù)據(jù),如new = a.add(b),返回一個新的tensor
2)會修改自身數(shù)據(jù),如a.add_(b),加法結(jié)果仍被存儲在a中,a的值被修改了
函數(shù)名以_結(jié)尾的都是inplace方式,即會修改調(diào)用者自己數(shù)據(jù)的函數(shù)
?
?
?1)Tensor()
Tensor函數(shù)新建tensor是最復(fù)雜多變的方式,它既可以接收一個list,并根據(jù)list的數(shù)據(jù)新建tensor;也能夠根據(jù)指定的形狀來新建tensor;還能傳入其他的tensor
1》指定形狀
#指定tensor的形狀 a = t.Tensor(2,3) a #數(shù)值取決于內(nèi)存空間的狀態(tài)返回:
tensor([[1.3733e-14, 6.4069e+02, 4.3066e+21],[1.1824e+22, 4.3066e+21, 6.3828e+28]])?
2》用list數(shù)據(jù)創(chuàng)建tensor
b = t.tensor([[1,2,3], [4,5,6]]) b返回:
tensor([[1, 2, 3],[4, 5, 6]])?
1 > 將tensor轉(zhuǎn)換成list——tolist():
b.tolist()返回:
[[1, 2, 3], [4, 5, 6]]?
2> 大小
1.tensor.size()返回torch.Size對象,它是tuple的子類,但其使用方式與tuple略有區(qū)別
b_size = b.size() b_size返回:
torch.Size([2, 3])?
2.tensor.shape直接查看tensor的形狀
b.shape返回:
torch.Size([2, 3])?
3> 元素個數(shù)
b.numel() #b中元素總個數(shù),2*3,等價于b.nelement()都返回6
?
3》創(chuàng)建一個和b形狀一樣的tensor
c = t.Tensor(b_size) c返回:
tensor([[0.0000e+00, 4.6566e-10, 3.8301e+33],[3.6902e+19, 2.8026e-45, 0.0000e+00]])?
4》指定元素創(chuàng)建tensor
d = t.Tensor((2,3)) d返回:
tensor([2., 3.])??t.Tensor(*size)創(chuàng)建tensor時,系統(tǒng)不會馬上分配空間,只會計算剩余的內(nèi)存是否足夠使用,使用到tensor時才會分配,而其他操作都是在創(chuàng)建完成后馬上進行空間分配
?
2.其他操作
?1)ones()
t.ones(2,3)返回:
tensor([[1., 1., 1.],[1., 1., 1.]])?
2)zeros()
t.zeros(2,3)返回:
tensor([[0., 0., 0.],[0., 0., 0.]])?
3)arange(s, t, step):得到s到t的值,步長為step,默認為1,前閉后開
t.arange(1,6)返回:
tensor([1, 2, 3, 4, 5])?
t.arange(1,6,2)返回:
tensor([1, 3, 5])?
也有range():前閉后閉,這個方法已經(jīng)過時了,建議使用arange()
k = t.range(0,10,2) k返回:
tensor([ 0., 2., 4., 6., 8., 10.])?
4)linspace(s, e, steps):將s到e均勻分成steps份
t.linspace(1,10,3)返回:
tensor([ 1.0000, 5.5000, 10.0000])?
5)randn()標(biāo)準(zhǔn)分布:從標(biāo)準(zhǔn)正態(tài)分布(均值為0,方差為 1,即高斯白噪聲)中抽取一組隨機數(shù)
t.randn(2,3)返回:
tensor([[-0.6179, 0.9102, -0.3926],[ 0.8710, -1.5552, 1.3961]])?
rand()均勻分布:返回一個張量,包含了從區(qū)間[0,1)的均勻分布中抽取的一組隨機數(shù)
t.rand(2,3)返回:
tensor([[0.3262, 0.9046, 0.6909],[0.3090, 0.7856, 0.9516]])?
6)randperm(m):長度為m,返回一個從0 到m-1 的隨機整數(shù)排
t.randperm(5)返回:
tensor([0, 3, 4, 2, 1])?
7)eye():對角線為1
t.eye(2,3)返回:
tensor([[1., 0., 0.],[0., 1., 0.]])?
8)等比數(shù)列:返回一個1維張量,包含在區(qū)間 10start和 10end上以對數(shù)刻度均勻間隔的steps個點
logspace(start, end, steps, out)
steps:生成的樣本數(shù)
out(Tensor, optional) : 結(jié)果張量
k = t.logspace(-10, 10, 5) k返回:
tensor([1.0000e-10, 1.0000e-05, 1.0000e+00, 1.0000e+05, 1.0000e+10])?
?3.常用Tensor操作
1)tensor.view():用于調(diào)整tensor的形狀,當(dāng)然需要保證調(diào)整前后元素總數(shù)一致
a = t.arange(0,6) print(a) a.view(2,3)返回:
tensor([0, 1, 2, 3, 4, 5]) tensor([[0, 1, 2],[3, 4, 5]])?
view不會修改自身的數(shù)據(jù),返回的新tensor與源tensor共享內(nèi)存,這樣只要更改其中一個,另一個也會跟著改變
所以這時候再打印a可見返回仍是之前的樣子,沒有被改變:
print(a)返回:
tensor([0, 1, 2, 3, 4, 5])?
參數(shù)為-1的作用:當(dāng)某一個維度設(shè)置為-1時,計算機會自動根據(jù)另一個維度的大小去計算該維度的值
b = a.view(-1, 3) b返回:
tensor([[0, 1, 2],[3, 4, 5]])?
這時候改變a可以發(fā)現(xiàn)b也跟著改變,因為共享內(nèi)存:
a[1] = 100 b返回:
tensor([[ 0, 100, 2],[ 3, 4, 5]])?
2)squeeze/unsqueeze:需要添加或減少某一維度,不會更改原來的數(shù)據(jù),而是生成新的tensor
1》unsqueeze(i):在i位置添加維度1
b.unsqueeze(1)返回:
tensor([[[0, 1, 2]],[[3, 4, 5]]])即b從2*3變成了2*1*3
等價于:
b.unsqueeze(-2) #表示倒數(shù)第二個維度?
2》squeeze(i):刪除i位置的維度1
c = b.view(1,1,1,2,3) c返回:
tensor([[[[[0, 1, 2],[3, 4, 5]]]]])?
c.squeeze(0) #刪除第0維的1返回:
tensor([[[[0, 1, 2],[3, 4, 5]]]])可見從1*1*1*2*3變成了1*1*2*3
?
可以不指定位置,默認是刪除所有的1維度:
c.squeeze()返回:
tensor([[0, 1, 2],[3, 4, 5]])可見從1*1*2*3變?yōu)榱?*3
?
3)resize:用來調(diào)整size,與view的區(qū)別在于它可以修改原來tensor的尺寸
1》如果小于,則之前的數(shù)據(jù)依然會被保存
b.resize_(1,3)返回,這里因為上面修改過:
tensor([[ 0, 100, 2]])?
2》如果新尺寸超過了原尺寸,就會自動分配新的內(nèi)存空間;
b.resize_(3,3)返回:
tensor([[ 0, 100, 2],[ 3, 4, 5],[ 0, 0, 0]])可見上面小于的操作并沒有丟失超出的數(shù)據(jù)
?
4)索引操作
索引出來的結(jié)果與原tensor共享內(nèi)存,即修改一個,另一個也會跟著修改
a[0] #第0行返回:
tensor([ 1.7488, -0.5191, -0.4163, 0.3690])?
a[:,0] #第0列返回:
tensor([ 1.7488, -1.9458, 1.1511])?
a[0][2]#第0行第2個元素返回:
tensor(-0.4163)?
a[0,-1] #第0行最后一個元素返回:
tensor(0.3690)?
a[:2] #前兩行,省略列則默認所有列返回:
tensor([[ 1.7488, -0.5191, -0.4163, 0.3690],[-1.9458, -0.1565, 1.4890, -0.0370]])?
a[:2,0:2] #前兩行,第0,1列返回:
tensor([[ 1.7488, -0.5191],[-1.9458, -0.1565]])?
print(a[0:1, :2]) #兩者等價,第0行,前兩列 print(a[0, :2])返回:
tensor([[ 1.7488, -0.5191]]) tensor([ 1.7488, -0.5191])?
a > 1 #判斷a中大于1的值,大于為1,小于為0返回:
tensor([[1, 0, 0, 0],[0, 0, 1, 0],[1, 0, 0, 0]], dtype=torch.uint8)?
a[a>1]#得到大于1的值,等價于a.masked_select(a>1),結(jié)果與原tensor不共享內(nèi)存空間返回:
tensor([1.7488, 1.4890, 1.1511])?
a[t.LongTensor([0,1])] #第0,1行返回:
tensor([[ 1.7488, -0.5191, -0.4163, 0.3690],[-1.9458, -0.1565, 1.4890, -0.0370]])?
5)常用選擇函數(shù):
1》gather:是一個比較復(fù)雜的操作,并不會更改原數(shù)據(jù)
dim和index的設(shè)置之間的關(guān)系是,對于一個二維tensor:
out[i][j] = input[index[i][j]][j] #當(dāng)dim = 0時 out[i][j] = input[i][index[i][j]] #當(dāng)dim = 1時如果是一個三維tensor:
out[i][j][k] = input[index[i][j][k]][j][k] #當(dāng)dim = 0時 out[i][j][k] = input[i][index[i][j][k]][k] #當(dāng)dim = 1時 out[i][j][k] = input[i][j][index[i][j][k]] #當(dāng)dim = 2時可見dim設(shè)置為什么,對應(yīng)的維度的索引就由index[i][j][k]來替換
?
舉個二維的例子:
1.
a = t.Tensor([[1,2],[3,4]]) a??index的類型必須是LongTensor類型的
#將第0行,第一個元素改成第0個元素 #將第二行元素互換 index = t.LongTensor([[0,0],[1,0]]) a.gather(1, index)返回:
tensor([[1., 1.],[4., 3.]])?
2.
a = t.arange(0,16).view(4,4) a?
#選取對角線的元素 index = t.LongTensor([[0,1,2,3]]) index.size()返回:
torch.Size([1, 4]) a.gather(0,index)返回:
tensor([[ 0, 5, 10, 15]])只定義了第一行的index,所以只會得到a[0][0] = a[0][0],a[0][1] = a[1][1],a[0][2] = a[2][2],a[0][3] = a[3][3],其他行因為沒有index,所以不會得到值,所以得到了對角線的值,該index更改了a第一個索引的下標(biāo)
?
index = t.LongTensor([[3,2,1,0]]) index返回:
tensor([[3, 2, 1, 0]])使用.t()進行轉(zhuǎn)置,返回4*1
index = t.LongTensor([[3,2,1,0]]).t() index返回:
tensor([[3],[2],[1],[0]])使用轉(zhuǎn)置后的index實現(xiàn)選取對角線的元素
a.gather(1,index)返回:
tensor([[ 3],[ 6],[ 9],[12]])即得到a[0][0] = a[0][3],a[1][0] = a[1][2],a[2][0] = a[2][1],a[3][0] = a[3][0]
?
#選取兩個對角線的元素 index = t.LongTensor([[0,1,2,3],[3,2,1,0]]).t() index返回:
tensor([[0, 3],[1, 2],[2, 1],[3, 0]])?
b = a.gather(1,index) b返回:
tensor([[ 0, 3],[ 5, 6],[10, 9],[15, 12]])?
gather的逆操作是scatter_:out = scatter_(dim, index, input)
gather把數(shù)據(jù)從input中按index取出
scatter_把取出的數(shù)據(jù)再放回去
#把上面得到的兩個對角線元素分別放進原來位置 c = t.zeros(4,4) c.scatter_(1,index,b.float())返回:
tensor([[ 0., 0., 0., 3.],[ 0., 5., 6., 0.],[ 0., 9., 10., 0.],[12., 0., 0., 15.]])??如果上面不寫成b.float()會報錯:
RuntimeError: Expected object of scalar type Float but got scalar type Long for argument #4 'src'?
2》index_select(input, dim, index, out=None):->Tensor
沿著指定維度對輸入進行切片,取index中指定的相應(yīng)項,index是一個LongTensor,然后返回一個新的張量。返回的張量與原始張量在指定軸上有著相同的維度
返回的張量與原始張量不共享內(nèi)存空間
a = t.rand(2,3) a返回:
tensor([[0.1724, 0.9527, 0.1607],[0.9276, 0.1666, 0.6390]])?
a_select = t.index_select(a, 1, t.LongTensor([0,2])) a_select返回:
tensor([[0.1724, 0.1607],[0.9276, 0.6390]])?
3》masked_select(input, mask, out=None)
根據(jù)傳入的ByteTensor類型的mask,來得到1所對應(yīng)的元素,返回一個一維張量
張量 mask和input張量必須有相同數(shù)量的元素數(shù)目,但形狀或維度不需要相同
返回的張量與原始張量不共享內(nèi)存空間
a = t.rand(2,3) a返回:
tensor([[0.1724, 0.9527, 0.1607],[0.9276, 0.1666, 0.6390]])?
mask = t.ByteTensor([[0, 1, 0], [1, 1, 0]]) a_mask = t.masked_select(a, mask) a_mask返回:
tensor([0.9527, 0.9276, 0.1666])?
4》nonzero(input, out=None)選擇非零值
返回一個包含輸入input中非零元素索引的張量,輸出張量中的每行包含輸入中非零元素的索引
a = t.Tensor([[1, 0, 3, 0, 2], [0, 2, 0, 2, 1]]) a返回:
tensor([[1., 0., 3., 0., 2.],[0., 2., 0., 2., 1.]])?
a_nonzero = t.nonzero(a) #返回的是非零元素的索引 a_nonzero返回:
tensor([[0, 0],[0, 2],[0, 4],[1, 1],[1, 3],[1, 4]])?
6)高級索引——一般不與原始的Tensor共享內(nèi)存
x = t.arange(0,27).view(3,3,3) x返回:
tensor([[[ 0, 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]]])?
同時訪問兩個值:
x[[1,2], [1,2], [2,0]] #訪問的是x[1,1,2]和x[2,2,0]返回:
tensor([14, 24])?
同時訪問三個值:
x[[2,1,0], [0], [1]] #訪問的是x[2, 0, 1],x[1, 0, 1],x[0, 0, 1]返回:
tensor([19, 10, 1])?
...代表剩下的都取:
x[[0, 2], ...] #取x[0],x[2]返回:
tensor([[[ 0, 1, 2],[ 3, 4, 5],[ 6, 7, 8]],[[18, 19, 20],[21, 22, 23],[24, 25, 26]]])?
4.Tensor類型
?Tensor有不同的數(shù)據(jù)類型,每種類型分別對應(yīng)有CPU和GPU版本(HalfTensor除外)
默認的tensor類型是FloatTensor,可通過t.set_default_tensor_type()修改默認的tensor類型(如果默認類型為GPU tensor,則所有操作都將在GPU上進行)
tensor的類型對分析內(nèi)存占用很有幫助
比如一個size為(1000, 1000, 1000)的FloatTensor,它有1000*1000*1000 = 10^9個元素,每個元素占32bit/8 = 4 Byte字節(jié)內(nèi)存,所以這整個tensor占4GB內(nèi)存/顯存
HalfTensor是專門為GPU版本設(shè)計的,同樣的元素個數(shù),顯存占用只有FloatTensor的一半,所以可以極大地緩解GPU顯存不足的問題,但由于HalfTensor所能表示的數(shù)值大小和精度有限,所以可能出現(xiàn)溢出等問題
各數(shù)據(jù)類型之間可以相互轉(zhuǎn)換,type(new_type)是通用的做法,當(dāng)然,你也可以使用.float(),.long(),.half()等快捷方法
CPU和GPU tensor之間的互相轉(zhuǎn)換通過tensor.cuda和tensor.cpu的方法實現(xiàn)
Tensor還有一個new方法,用法和t.Tensor一樣,會調(diào)用該tensor對應(yīng)類型的構(gòu)造函數(shù),生成與當(dāng)前tensor類型一致的tensor
?
首先查看一下默認的tensor類型:
t.get_default_dtype()返回:
torch.float32?
想將默認類型設(shè)置為IntTensor類型,但是出錯:
#設(shè)置默認tensor,注意參數(shù)是字符串 t.set_default_tensor_type('torch.IntTensor')報錯:
TypeError: only floating-point types are supported as the default type意思是說只能設(shè)置float類型的值為默認類型,所以設(shè)置為torch.CharTensor也會報錯,只能設(shè)置為float,double和half
如果設(shè)置為:
#設(shè)置默認tensor,注意參數(shù)是字符串 t.set_default_tensor_type('torch.DoubleTensor')就成功:
t.get_default_dtype()返回:
torch.float64?
然后生成一個值
a = t.Tensor(2, 3) a返回:
tensor([[-1.2882e-231, 1.7306e-77, 6.2817e+98],[ 7.7417e-315, -1.2882e-231, 4.2154e-309]])查看該值的類型:
a.dtype #返回a的類型果然是float64的類型:
torch.float64?
把a轉(zhuǎn)成FloatTensor類型,等價于b = a.type(t.FloatTensor)
b = a.float() b返回:
tensor([[-0., 0., inf],[0., -0., 0.]], dtype=torch.float32)對于32bit的float來說,上面生成的double值都過大或過小,超過其能承受范圍了
?
type_as(tensor): 等價于self.type(tensor.type()),將tensor投射為參數(shù)給定tensor類型并返回。 如果tensor已經(jīng)是正確的類型則不會執(zhí)行操作
c = a.type_as(b) c返回:
tensor([[-0., 0., inf],[0., -0., 0.]], dtype=torch.float32)即將a投射為b的類型,然后將值返回到c中,因為溢出,所以c的值和b相同
兩者不共享內(nèi)存空間
c[0] = 1.0 c返回:
tensor([[1., 1., 1.],[0., -0., 0.]], dtype=torch.float32)a不變
?
new(): 構(gòu)建一個有相同數(shù)據(jù)類型的tensor
此時a為:
tensor([[0., 0., 0.],[0., 0., 0.]])生成b:
d = a.new(2,3) d返回:
tensor([[0., 0., 0.],[0., 0., 0.]])恢復(fù)之前的設(shè)置:
t.set_default_tensor_type('torch.FloatTensor')?
5.逐元素操作
對tensor的每一個元素(point-wise,又名element-wise)進行操作,此操作的輸入與輸出形狀一致
當(dāng)然,上面的一些操作如div,mul,pow,fmod等都實現(xiàn)了運算符重載,可以直接使用運算符
如a**2等價于torch.pow(a, 2), a*2等價于torch.mul(a, 2),a%2等價于torch.fmod(a,2)
?
1)clamp: 常用在某些需要比較大小的地方,如取一個tensor的每個元素與另一個數(shù)的較大值
a = t.arange(0, 6).view(2,3) a返回:
tensor([[0, 1, 2],[3, 4, 5]])比較大小:
t.clamp(a, min=3) #取a中元素與3相比其中最大的值返回:
tensor([[3, 3, 3],[3, 4, 5]])?
6.歸并操作
此操作會使輸出形狀小于輸入形狀,并可以沿著某一維度進行指定操作
比如操作.sum(),既可以計算整個tensor的和,也可以計算tensor中每一行或每一列的和
上面的操作都有一個參數(shù)dim,用來指定這些操作是在哪個維度上執(zhí)行的
如輸入的形狀是(m, n, k)
- 如果指定dim = 0,輸出的形狀就是(1,n,k)或(n,k),即對行進行操作
- 如果指定dim = 1,輸出的形狀就是(m,1,k)或(m,k),即對列進行操作
- 如果指定dim = 2,輸出的形狀就是(m,n,1)或(m,n),即對第三維進行操作
輸出size中是否有1將取決于參數(shù)keepdim,如果keepdim = True則會保留維度1,這里keepdim默認為False
但并不是所有的函數(shù)都符合這種形狀變化方式,如cumsum
?
b = t.ones(2,3) #進行求和操作 b.sum(dim = 0, keepdim = True)返回:
tensor([[2., 2., 2.]])查看此時的size:
b.sum(dim = 0, keepdim = True).size()返回:
torch.Size([1, 3])如果這里不設(shè)keepdim = True,效果為:
b.sum(dim = 0)返回:
tensor([2., 2., 2.])大小為:
torch.Size([3])?
如果將dim設(shè)置為1
b.sum(dim=1)返回:
tensor([3., 3.])?
cunsum有自己的計算方式,是每行逐層進行累加:
a = t.arange(0,6).view(2,3) print(a) a.cumsum(dim=1)#沿著行累加返回:
tensor([[0, 1, 2],[3, 4, 5]]) tensor([[ 0, 1, 3],[ 3, 7, 12]])?
7.比較函數(shù)
這些函數(shù)有逐元素的,也有類似歸并操作的
其中第一行的比較操作實現(xiàn)了運算符重載,等價于a>=b,a>b,a!=b和a==b,其返回結(jié)果是一個ByteTensor,可用來選取元素
max/min這兩個操作比較特殊,以max為例:
- t.max(tensor) : 返回tensor中最大的一個數(shù)
- t.max(tensor, dim) : 指定維上最大的數(shù),返回tensor和下標(biāo)
- t.max(tensor1, tensor2) : 比較兩個tensor,得到對應(yīng)位置比較大的元素
?
1)比較函數(shù)
a = t.linspace(0,15,6).view(2,3) a返回:
tensor([[ 0., 3., 6.],[ 9., 12., 15.]])?
b = t.linspace(15, 0, 6).view(2,3) b返回:
tensor([[15., 12., 9.],[ 6., 3., 0.]])比較:
a>b返回:
tensor([[0, 0, 0],[1, 1, 1]], dtype=torch.uint8)uint8就是ByteTensor類型
然后我們就能夠使用上面得到餓結(jié)果去獲取a>b的值:
a[a>b]返回:
tensor([ 9., 12., 15.])大小為:
torch.Size([3])?
2)最值函數(shù)
#既max(tensor):得到tensor中最大的值 t.max(a)返回:
tensor(15.)?
t.max(b, dim=0)#得到的是列中最大的值 #第一個返回值是最大的值 #第二個返回值是這些值是該列的第幾個元素返回:
(tensor([15., 12., 9.]), tensor([0, 0, 0]))?
t.max(b, dim=1)#得到的是行中最大的值 #第一個返回值是最大的值 #第二個返回值是這些值是該行的第幾個元素返回:
(tensor([15., 6.]), tensor([0, 0]))?
比較兩個tensor:
t.max(a, b)返回:
tensor([[15., 12., 9.],[ 9., 12., 15.]])?
3)比較一個tensor和一個數(shù),可以使用clamp函數(shù)
#將a中各個位置的值和10比較,得到其中更大的值 t.clamp(a, min=10)返回:
tensor([[10., 10., 10.],[10., 12., 15.]])?
8.線性代數(shù)
?
1)trace() → float
a = t.Tensor([[1, 0, 3], [0, 2, 0], [1, 1, 1]]) a求對角線元素之和:
k = t.trace(a) #1+2+1 k返回值的類型是floatTensor:
tensor(4.)?
2)diag():得到對角線元素
k = t.diag(a) k返回:
tensor([1., 2., 1.])?
3)triu(input, diagonal=0, out=None) → Tensor上三角/tril()下三角
偏移量diagonal默認為0
u = t.triu(a)#上三角 u返回:
tensor([[1., 0., 3.],[0., 2., 0.],[0., 0., 1.]])?
l = t.tril(a) #下三角 l返回:
tensor([[1., 0., 0.],[0., 2., 0.],[1., 1., 1.]])?
偏移量:
- diagonal = 0,保留主對角線與主對角線以上/下的元素;
- diagonal = n,保留主對角線與主對角線向上/下n行的元素;
- diagonal = -n,保留主對角線上/下方h行后的對角線的元素;
返回:
tensor([[1., 0., 0., 0., 0.],[0., 2., 0., 0., 0.],[1., 1., 1., 2., 0.],[1., 3., 4., 5., 2.],[3., 2., 1., 1., 2.]])?
l2 = t.tril(a, diagonal=2) #下三角,既除了下三角還多得到斜對角向上多兩行的值 l2返回:
tensor([[1., 0., 3., 0., 0.],[0., 2., 0., 3., 0.],[1., 1., 1., 2., 2.],[1., 3., 4., 5., 2.],[3., 2., 1., 1., 2.]])?
l3 = t.tril(a, diagonal=-2) #下三角,得到從對角線開始刪除兩行的值 l3返回:
tensor([[0., 0., 0., 0., 0.],[0., 0., 0., 0., 0.],[1., 0., 0., 0., 0.],[1., 3., 0., 0., 0.],[3., 2., 1., 0., 0.]])?
4)torch.mm(mat1, mat2, out=None) → Tensor矩陣乘法
對矩陣mat1和mat2進行相乘。 如果mat1?是一個n*m張量,mat2?是一個?m*p 張量,將會輸出一個?n×p 張量out
z1 = t.Tensor([[1,2,3],[2,3,4]]) z1返回:
tensor([[1., 2., 3.],[2., 3., 4.]])?
z2 = t.Tensor([[1,2],[3,4],[5,6]]) z2返回:
tensor([[1., 2.],[3., 4.],[5., 6.]])相乘
t.mm(z1, z2)返回:
tensor([[22., 28.],[31., 40.]])?
5)轉(zhuǎn)置
矩陣的轉(zhuǎn)置會導(dǎo)致存儲空間不連續(xù),需要調(diào)用其的.contiguous方法將其轉(zhuǎn)為連續(xù)
b = a.t() b.is_contiguous() #查看空間是否連續(xù)返回:
False但是顯示b為:
tensor([[ 0., 9.],[ 3., 12.],[ 6., 15.]])與調(diào)用.contiguous方法返回的值是相同的
b.contiguous().is_contiguous() #返回true?
9.Tensor和Numpy
Numpy和Tensor共享內(nèi)存
由于Numpy歷史悠久,支持豐富的操作,所以當(dāng)遇見Tensor不支持的操作的時候,可以先將其轉(zhuǎn)成Numpy,處理完后再轉(zhuǎn)回tensor,其轉(zhuǎn)換開銷很小
import numpy as np a = np.ones([2,3], dtype = np.float32) a返回:
array([[1., 1., 1.],[1., 1., 1.]], dtype=float32)?
b = t.from_numpy(a) b返回:
tensor([[1., 1., 1.],[1., 1., 1.]])查看b類型:
b.dtype?
#另一種生成b的方法,這種情況下若numpy不是float32則會新建 b = t.Tensor(a) b返回相同
?
c = b.numpy() c返回:
array([[1., 1., 1.],[1., 1., 1.]], dtype=float32)?
#a,b,c共享內(nèi)存,一個改變,其他的也跟著改變 a[0,1] = 100 a返回:
array([[ 1., 100., 1.],[ 1., 1., 1.]], dtype=float32)查看b,c的值發(fā)現(xiàn)也是這個
?
10.自動廣播
- pytorch支持自動廣播法則 :快速執(zhí)行向量化的同時不會占用額外的內(nèi)存/顯存
法則定義:
- 讓所有輸入數(shù)組向其中shape最長的數(shù)組看棄,shape不足的數(shù)組在前面添加1
- 當(dāng)輸入法則的某個維度的長度為1時,計算時沿此維度復(fù)制擴充成一樣的形狀
返回:
tensor([[[1., 1.],[1., 1.],[1., 1.]],[[1., 1.],[1., 1.],[1., 1.]]])自動廣播法則為:
1. 因為a比b少1維,所以在a前面補1,形狀變?yōu)?1,3,2)。等價于a.unsqueeze(0)
2.a和b的第一和三維形狀不一樣,進行擴展,將他們的形狀都變成(2,3,2)
?
- 也可以通過函數(shù)實現(xiàn)手動廣播,這樣更直觀,更不易出錯:
unsqueeze或view : 為數(shù)據(jù)某一維的形狀補1
expand或expand_as :重復(fù)數(shù)組,該操作不會復(fù)制數(shù)組,所以不會占用額外空間
??repeat實現(xiàn)與expand相類似的功能,不使用它的原因是repeat會把相同數(shù)據(jù)復(fù)制多份,因此會占用額外的空間
既上面的例子可以寫成:
a.unsqueeze(0).expand(2,3,2) + b.expand(2,3,2)?
11.內(nèi)部結(jié)構(gòu)
tensor分為頭信息區(qū)(Tensor)和存儲區(qū)(Storage)
信息區(qū)主要保存著tensor的形狀(size)、步長(stride)、數(shù)據(jù)類型(type)等信息,而真正的數(shù)據(jù)則保存成連續(xù)數(shù)組,存儲在存儲區(qū)
因為數(shù)據(jù)動輒成千上萬,因此信息區(qū)元素占用內(nèi)存較少,主要內(nèi)存占用取決于tensor中元素的數(shù)目,即存儲區(qū)的大小
?
一般來說,一個tensor有著與之相對應(yīng)的storage,storage是在data之上封裝的接口,便于使用
不同的tensor的頭信息一般不同,但是可能使用相同的storage
生成a:
a = t.arange(0,6) a.storage()??將這里改成a = t.arange(0,6).float(),用來保證得到的值的類型為FloatTensor
這跟下面遇見的一個問題相關(guān),可以看到下面了解一下,然后再跟著操作
所以你的下面內(nèi)容的值的類型應(yīng)該為FloatTensor類型,我的仍是LongTensor,因為我沒有改過來
返回:
012345 [torch.LongStorage of size 6]生成b:
b = a.view(2,3) b.storage()返回:
012345 [torch.LongStorage of size 6]對比兩者內(nèi)存地址:
#一個對象的id值可以看作她的內(nèi)存空間 #a,b storage的內(nèi)存地址一樣,即是同一個storage id(a.storage()) == id(b.storage())返回:
True改變某個值查看是否共享內(nèi)存:
#a改變,b也隨之改變,因為他們共享storage,即內(nèi)存 a[1] = 100 b返回:
tensor([[ 0, 100, 2],[ 3, 4, 5]])生成c:
#c從a的后兩個元素取起 c = a[2:] c.storage()#指向相同返回:
01002345 [torch.LongStorage of size 6]查看其首元素內(nèi)存地址:
c.data_ptr(), a.data_ptr() #data_ptr返回tensor首元素的內(nèi)存地址 #從結(jié)果可以看出兩者的地址相差16 #因為c是從a第二個元素選起的,每個元素占8個字節(jié),因為a的值的類型是int64返回:
(140707162378192, 140707162378176)因為查看后a的類型為int64:
a.dtype返回:
torch.int64更改c:
c[0] = -100 #a,c也共享內(nèi)存空間,c[0]的內(nèi)存地址對應(yīng)的是a[2]的內(nèi)存地址 a返回:
tensor([ 0, 100, -100, 3, 4, 5])?
使用storage來生成新tensor:
d = t.Tensor(c.storage())#這樣a,b,c,d共享同樣的內(nèi)存空間 d[0] = 6666 b??報錯:
RuntimeError: Expected object of data type 6 but got data type 4 for argument #2 'source'這是因為Tensor期待得到的值的類型是FloatTensor(類型6),而不是其他類型LongTensor(data type 4)
因為如果生成:
dtypea = t.FloatTensor([[1, 2, 3], [4, 5, 6]]) dtypea.storage()返回:
1.02.03.04.05.06.0 [torch.FloatStorage of size 6]再運行就成功了:
d = t.Tensor(dtypea.storage())#這樣a,b,c,d共享同樣的內(nèi)存空間 d[0] = 6666 dtypea返回:
tensor([[6.6660e+03, 2.0000e+00, 3.0000e+00],[4.0000e+00, 5.0000e+00, 6.0000e+00]])如果使用的是IntTensor(data type 3),也會報錯:
RuntimeError: Expected object of data type 6 but got data type 3 for argument #2 'source'ShortTensor(data type 2),CharTensor(data type 1),ByteTensor(data type 0),DoubleTensor(data type 7)
?
下面的操作會在將上面的值改成FloatTensor的基礎(chǔ)上進行,即在a = t.arange(0,6)后面添加.float(),然后從頭執(zhí)行了一遍
d = t.Tensor(c.storage())#這樣a,b,c,d共享同樣的內(nèi)存空間 d[0] = 6666 b返回:
tensor([[ 6.6660e+03, 1.0000e+02, -1.0000e+02],[ 3.0000e+00, 4.0000e+00, 5.0000e+00]])判斷是否共享內(nèi)存:
#因此a,b,c,d這4個tensor共享storage id(a.storage()) ==id(b.storage()) ==id(c.storage()) ==id(d.storage())#返回True偏移量:
#獲取首元素相對于storage地址的偏移量 a.storage_offset(), c.storage_offset(), d.storage_offset()返回:
(0, 2, 0)即使使用索引只獲得一部分值,指向仍是storage:
#隔兩行/列取元素來生成e e = b[::2,::2] print(e) print(e.storage_offset()) id(e.storage()) ==id(a.storage()) #雖然值不相同,但是得到的storage是相同的返回:
tensor([[6666., -100.]]) 0 Out[44]: True步長信息:是有層次結(jié)構(gòu)的步長
#獲得步長信息 b.stride(), e.stride()返回:
((3, 1), (6, 2))查看空間是否連續(xù):
#查看其值的內(nèi)存空間是否連續(xù) #因為e只取得了storage中的部分值,因此其是不連續(xù)的 b.is_contiguous(), e.is_contiguous()返回:
(True, False)從上面的操作中我們可以看出絕大多數(shù)的操作并不修改tensor的數(shù)據(jù),即存儲區(qū)的內(nèi)容,只是修改了頭信息區(qū)的內(nèi)容
這種做法更節(jié)省內(nèi)存,同時提升了處理速度
但是我們可以看見e的操作導(dǎo)致其不連續(xù),這時候可以調(diào)用tensor.contiguous()方法將他們變成連續(xù)的數(shù)據(jù)。該方法是復(fù)制數(shù)據(jù)到新的內(nèi)存中,不再與原來的數(shù)據(jù)共享storage,如:
e.contiguous().is_contiguous() #返回True生成f:
print(e.data_ptr()) f = e.contiguous() print(f.data_ptr()) #可見為f新分配了內(nèi)存空間 print(f) print(f.storage())#內(nèi)存空間中只有兩個值 print(f.size()) print(e.data_ptr()) #e指向的內(nèi)存沒有改變 f.is_contiguous() #這里的f的內(nèi)存空間是連續(xù)的返回:
140707203003760 140707160267104 tensor([[6666., -100.]])6666.0-100.0 [torch.FloatStorage of size 2] torch.Size([1, 2]) 140707203003760 Out[56]: True是否為連續(xù)內(nèi)存空間有什么影響?
比如當(dāng)你想要使用.view()轉(zhuǎn)換tensor的形狀時,如果該tensor的內(nèi)存空間不是連續(xù)的則會報錯:
報錯:
RuntimeError: invalid argument 2: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Call .contiguous() before .view(). at /Users/soumith/mc3build/conda-bld/pytorch_1549593514549/work/aten/src/TH/generic/THTensor.cpp:213報錯的意思也是要求在.view()之前調(diào)用.contiguous(),改后為:
k = t.arange(0,6).view(2,3).float().t()#進行轉(zhuǎn)置,轉(zhuǎn)置后的k內(nèi)存是不連續(xù)的 k.is_contiguous() k.contiguous().view(-1)成功返回:
tensor([0., 3., 1., 4., 2., 5.])?
12.其他與tensor相關(guān)內(nèi)容
1)持久化
Tensor的保存和加載十分簡單,使用t.save和t.load即可完成相應(yīng)的功能
在save/load時可指定使用的pickle模塊,在load時還可以將CPU tensor映射到CPU或其他GPU上
if t.cuda.is_available():a = a.cuda(1) #把a轉(zhuǎn)為GPU1上的tensort.save(a, 'a.pth')#加載為b,存儲與GPU1上(因為保存時tensor就在GPU1上)b = t.load('a.pth')#加載為c,存儲于CPUc = t.load('a.pth', map_location = lambda storage, loc:storage)#加載為d,存儲于GPU0上d = t.load('a.pth', map_location={'cuda:1':'cuda:0'})?
2)向量化
向量化計算是一種特殊的并行計算方式,一般程序在同一時間只執(zhí)行一個操作的方法,它可以在同一時間執(zhí)行多個操作,通常是對不同的數(shù)據(jù)執(zhí)行同樣的一個或一批指令,或者說把指令應(yīng)用于一個數(shù)組/向量上
向量化可極大程度地提高科學(xué)運算的效率,用來替代python中的for循環(huán)
舉例說明:
def for_loop_add(x,y):result = []for i,j in zip(x,y):result.append(i+j)return t.Tensor(result)生成x,y:
x = t.zeros(100) y = t.ones(100)使用for循環(huán):
import time start = time.time() for_loop_add(x,y) end = time.time() end-start返回:
0.0022869110107421875向量化:
import time start = time.time() x+y end = time.time() end-start返回:
0.00030493736267089844可見時間有近十倍的差距
??有幾點需要注意:
- 大多數(shù)t.function都會有一個參數(shù)out=None,產(chǎn)生的結(jié)果將保存在out指定的tensor之中
- t.set_num_threads可以設(shè)置PyTorch進行CPU多線程并行計算時所占用的線程,用來限制PyTorch所占用的CPU數(shù)目
- t.set_printpotions可以用來設(shè)置打印tensor時的數(shù)值精度和格式
下面舉例說明:
a = t.arange(0,32769).short() #int16(-32768 ~ 32767) print(a.dtype) print(a[-1],a[-2])#16bit的IntTensor精度有限導(dǎo)致溢出 b = t.IntTensor() t.arange(0,32769,out=b)#32bit的LongTensor就不會導(dǎo)致溢出了 b[-1],b[-2]返回:
torch.int16 tensor(-32768, dtype=torch.int16) tensor(32767, dtype=torch.int16) Out[10]: (tensor(32768, dtype=torch.int32), tensor(32767, dtype=torch.int32))?
a = t.randn(2,3) a返回:
tensor([[ 0.2182, -1.1149, 1.1781],[ 1.0245, 0.9339, 1.6028]])設(shè)置精度:
t.set_printoptions(precision=10)#將精度設(shè)為10 a返回:
tensor([[ 0.2181526423, -1.1149182320, 1.1780972481],[ 1.0244952440, 0.9339445829, 1.6027815342]])?
13.小試牛刀——線性回歸
線性回歸利用數(shù)理統(tǒng)計中的回歸分析來確定兩種或兩種以上變量之間相互依賴的定量關(guān)系,其表達式為y=wx +b +e,誤差e服從均值為0的正態(tài)分布。線性回歸的損失函數(shù)是:
利用隨機梯度下降法更新參數(shù)w,b來最小化損失函數(shù),最終學(xué)習(xí)得到w,b的數(shù)值
導(dǎo)入需要的包:
import torch as t from matplotlib import pyplot as plt from IPython import display生成訓(xùn)練數(shù)據(jù):
#設(shè)置隨機數(shù)種子,保證在不同計算機上運行時下面的輸出一致 t.manual_seed(1000) def get_fake_data(batch_size=8):'''產(chǎn)生隨機數(shù)據(jù):y=x*2+3,加上了些噪音點'''x = t.rand(batch_size, 1)*20 #生成size為(batch_size, 1)的二維數(shù)組,并元素乘20y = x * 2 + (1+t.rand(batch_size, 1))*3 #用于生成噪音return x,y測試看看會生成什么樣的數(shù)據(jù):
#來看看產(chǎn)生的x-y分布,輸出如圖所示 x, y = get_fake_data() plt.scatter(x.squeeze().numpy(), y.squeeze().numpy())圖示為:
生成參數(shù):
#隨機初始化參數(shù) w = t.rand(1,1) b = t.zeros(1,1)實現(xiàn):
lr = 0.001 #學(xué)習(xí)率for ii in range(2000): #2000次迭代x, y = get_fake_data() #生成數(shù)據(jù)#前向傳播forward,計算lossy_pred = x.mm(w) + b.expand_as(y)loss = 0.5*(y_pred - y)**2 #均方誤差就lossloss = loss.sum()#backward:手動計算梯度dloss = 1dy_pred = dloss *(y_pred - y)dw = x.t().mm(dy_pred)db = dy_pred.sum()#更新參數(shù)w.sub_(lr * dw)b.sub_(lr * db)if ii%1000 == 0:#沒1000次迭代,畫一次圖#畫圖display.clear_output(wait=True)x = t.arange(0, 20).view(-1,1).float()y = x.mm(w) + b.expand_as(x)plt.plot(x.numpy(), y.numpy()) #預(yù)測x2, y2 = get_fake_data(batch_size=20)plt.scatter(x2.numpy(), y2.numpy())plt.xlim(0, 20) plt.ylim(0, 41)plt.show()plt.pause(0.5) print(w.squeeze(),b.squeeze())返回:
tensor(1.9801) tensor(4.5172)上面得到的是學(xué)習(xí)到的w,b的值,看下面的圖可以看見直線和數(shù)據(jù)很好地實現(xiàn)了擬合
圖示為:?
?
轉(zhuǎn)載于:https://www.cnblogs.com/wanghui-garcia/p/10617338.html
總結(jié)
以上是生活随笔為你收集整理的深度学习框架PyTorch一书的学习-第三章-Tensor和autograd-1-Tensor的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavsScript中DOM的基本操作
- 下一篇: 你所不知道的ASP.NET Core M