參考https://github.com/chenyuntc/pytorch-book/tree/v1.0
希望大家直接到上面的網址去查看代碼,下面是本人的筆記
Tensor
Tensor可以是一個數(標量)、一維數組(向量)、二維數組(矩陣)或更高維的數組(高階數據)
Tensor和numpy的ndarrays類似,不同在於pytorch的tensor支持GPU加速
導包:
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
如果創建的是一個數值
k = 1 t.is_tensor(k)#返回False
1.基礎操作
從接口的角度將tensor的操作分為兩類:
1)torch.function,如torch.save()等
2)tensor.function,如tensor.view()等
對tensor的大部分操作同時支持這兩類接口,如torch.sum(a,b)等價於a.sum(b)
從存儲角度將tensor的操作分為兩類:
1)不會修改自身數據,如new = a.add(b),返回一個新的tensor
2)會修改自身數據,如a.add_(b),加法結果仍被存儲在a中,a的值被修改了
函數名以_結尾的都是inplace方式,即會修改調用者自己數據的函數
1)Tensor()
Tensor函數新建tensor是最復雜多變的方式,它既可以接收一個list,並根據list的數據新建tensor;也能夠根據指定的形狀來新建tensor;還能傳入其他的tensor
1》指定形狀
#指定tensor的形狀 a = t.Tensor(2,3) a #數值取決於內存空間的狀態
返回:
tensor([[1.3733e-14, 6.4069e+02, 4.3066e+21], [1.1824e+22, 4.3066e+21, 6.3828e+28]])
2》用list數據創建tensor
b = t.tensor([[1,2,3], [4,5,6]]) b
返回:
tensor([[1, 2, 3], [4, 5, 6]])
1 > 將tensor轉換成list——tolist():
b.tolist()
返回:
[[1, 2, 3], [4, 5, 6]]
2> 大小
1.tensor.size()返回torch.Size對象,它是tuple的子類,但其使用方式與tuple略有區別
b_size = b.size()
b_size
返回:
torch.Size([2, 3])
2.tensor.shape直接查看tensor的形狀
b.shape
返回:
torch.Size([2, 3])
3> 元素個數
b.numel() #b中元素總個數,2*3,等價於b.nelement()
都返回6
3》創建一個和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》指定元素創建tensor
d = t.Tensor((2,3)) d
返回:
tensor([2., 3.])
⚠️t.Tensor(*size)創建tensor時,系統不會馬上分配空間,只會計算剩余的內存是否足夠使用,使用到tensor時才會分配,而其他操作都是在創建完成后馬上進行空間分配
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():前閉后閉,這個方法已經過時了,建議使用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()標准分布:從標准正態分布(均值為0,方差為 1,即高斯白噪聲)中抽取一組隨機數
t.randn(2,3)
返回:
tensor([[-0.6179, 0.9102, -0.3926], [ 0.8710, -1.5552, 1.3961]])
rand()均勻分布:返回一個張量,包含了從區間[0,1)的均勻分布中抽取的一組隨機數
t.rand(2,3)
返回:
tensor([[0.3262, 0.9046, 0.6909], [0.3090, 0.7856, 0.9516]])
6)randperm(m):長度為m,返回一個從0 到m-1 的隨機整數排
t.randperm(5)
返回:
tensor([0, 3, 4, 2, 1])
7)eye():對角線為1
t.eye(2,3)
返回:
tensor([[1., 0., 0.], [0., 1., 0.]])
8)等比數列:返回一個1維張量,包含在區間 10start和 10end上以對數刻度均勻間隔的steps個點
logspace(start, end, steps, out)
steps:生成的樣本數
out(Tensor, optional) : 結果張量
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():用於調整tensor的形狀,當然需要保證調整前后元素總數一致
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不會修改自身的數據,返回的新tensor與源tensor共享內存,這樣只要更改其中一個,另一個也會跟着改變
所以這時候再打印a可見返回仍是之前的樣子,沒有被改變:
print(a)
返回:
tensor([0, 1, 2, 3, 4, 5])
參數為-1的作用:當某一個維度設置為-1時,計算機會自動根據另一個維度的大小去計算該維度的值
b = a.view(-1, 3) b
返回:
tensor([[0, 1, 2], [3, 4, 5]])
這時候改變a可以發現b也跟着改變,因為共享內存:
a[1] = 100 b
返回:
tensor([[ 0, 100, 2], [ 3, 4, 5]])
2)squeeze/unsqueeze:需要添加或減少某一維度,不會更改原來的數據,而是生成新的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) #表示倒數第二個維度
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變為了2*3
3)resize:用來調整size,與view的區別在於它可以修改原來tensor的尺寸
1》如果小於,則之前的數據依然會被保存
b.resize_(1,3)
返回,這里因為上面修改過:
tensor([[ 0, 100, 2]])
2》如果新尺寸超過了原尺寸,就會自動分配新的內存空間;
b.resize_(3,3)
返回:
tensor([[ 0, 100, 2], [ 3, 4, 5], [ 0, 0, 0]])
可見上面小於的操作並沒有丟失超出的數據
4)索引操作
索引出來的結果與原tensor共享內存,即修改一個,另一個也會跟着修改
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),結果與原tensor不共享內存空間
返回:
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)常用選擇函數:
1》gather:是一個比較復雜的操作,並不會更改原數據
dim和index的設置之間的關系是,對於一個二維tensor:
out[i][j] = input[index[i][j]][j] #當dim = 0時 out[i][j] = input[i][index[i][j]] #當dim = 1時
如果是一個三維tensor:
out[i][j][k] = input[index[i][j][k]][j][k] #當dim = 0時 out[i][j][k] = input[i][index[i][j][k]][k] #當dim = 1時 out[i][j][k] = input[i][j][index[i][j][k]] #當dim = 2時
可見dim設置為什么,對應的維度的索引就由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第一個索引的下標
index = t.LongTensor([[3,2,1,0]]) index
返回:
tensor([[3, 2, 1, 0]])
使用.t()進行轉置,返回4*1
index = t.LongTensor([[3,2,1,0]]).t() index
返回:
tensor([[3], [2], [1], [0]])
使用轉置后的index實現選取對角線的元素
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把數據從input中按index取出
scatter_把取出的數據再放回去
#把上面得到的兩個對角線元素分別放進原來位置 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中指定的相應項,index是一個LongTensor,然后返回一個新的張量。返回的張量與原始張量在指定軸上有着相同的維度
返回的張量與原始張量不共享內存空間
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)
根據傳入的ByteTensor類型的mask,來得到1所對應的元素,返回一個一維張量
張量 mask和input張量必須有相同數量的元素數目,但形狀或維度不需要相同
返回的張量與原始張量不共享內存空間
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共享內存
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有不同的數據類型,每種類型分別對應有CPU和GPU版本(HalfTensor除外)
默認的tensor類型是FloatTensor,可通過t.set_default_tensor_type()修改默認的tensor類型(如果默認類型為GPU tensor,則所有操作都將在GPU上進行)
tensor的類型對分析內存占用很有幫助
比如一個size為(1000, 1000, 1000)的FloatTensor,它有1000*1000*1000 = 10^9個元素,每個元素占32bit/8 = 4 Byte字節內存,所以這整個tensor占4GB內存/顯存
HalfTensor是專門為GPU版本設計的,同樣的元素個數,顯存占用只有FloatTensor的一半,所以可以極大地緩解GPU顯存不足的問題,但由於HalfTensor所能表示的數值大小和精度有限,所以可能出現溢出等問題
各數據類型之間可以相互轉換,type(new_type)是通用的做法,當然,你也可以使用.float(),.long(),.half()等快捷方法
CPU和GPU tensor之間的互相轉換通過tensor.cuda和tensor.cpu的方法實現
Tensor還有一個new方法,用法和t.Tensor一樣,會調用該tensor對應類型的構造函數,生成與當前tensor類型一致的tensor
首先查看一下默認的tensor類型:
t.get_default_dtype()
返回:
torch.float32
想將默認類型設置為IntTensor類型,但是出錯:
#設置默認tensor,注意參數是字符串 t.set_default_tensor_type('torch.IntTensor')
報錯:
TypeError: only floating-point types are supported as the default type
意思是說只能設置float類型的值為默認類型,所以設置為torch.CharTensor也會報錯,只能設置為float,double和half
如果設置為:
#設置默認tensor,注意參數是字符串 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轉成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投射為參數給定tensor類型並返回。 如果tensor已經是正確的類型則不會執行操作
c = a.type_as(b)
c
返回:
tensor([[-0., 0., inf], [0., -0., 0.]], dtype=torch.float32)
即將a投射為b的類型,然后將值返回到c中,因為溢出,所以c的值和b相同
兩者不共享內存空間
c[0] = 1.0 c
返回:
tensor([[1., 1., 1.], [0., -0., 0.]], dtype=torch.float32)
a不變
new(): 構建一個有相同數據類型的tensor
此時a為:
tensor([[0., 0., 0.], [0., 0., 0.]])
生成b:
d = a.new(2,3) d
返回:
tensor([[0., 0., 0.], [0., 0., 0.]])
恢復之前的設置:
t.set_default_tensor_type('torch.FloatTensor')
5.逐元素操作
對tensor的每一個元素(point-wise,又名element-wise)進行操作,此操作的輸入與輸出形狀一致
當然,上面的一些操作如div,mul,pow,fmod等都實現了運算符重載,可以直接使用運算符
如a**2等價於torch.pow(a, 2), a*2等價於torch.mul(a, 2),a%2等價於torch.fmod(a,2)
1)clamp: 常用在某些需要比較大小的地方,如取一個tensor的每個元素與另一個數的較大值
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中每一行或每一列的和
上面的操作都有一個參數dim,用來指定這些操作是在哪個維度上執行的
如輸入的形狀是(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將取決於參數keepdim,如果keepdim = True則會保留維度1,這里keepdim默認為False
但並不是所有的函數都符合這種形狀變化方式,如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])
如果這里不設keepdim = True,效果為:
b.sum(dim = 0)
返回:
tensor([2., 2., 2.])
大小為:
torch.Size([3])
如果將dim設置為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.比較函數
這些函數有逐元素的,也有類似歸並操作的
其中第一行的比較操作實現了運算符重載,等價於a>=b,a>b,a!=b和a==b,其返回結果是一個ByteTensor,可用來選取元素
max/min這兩個操作比較特殊,以max為例:
- t.max(tensor) : 返回tensor中最大的一個數
- t.max(tensor, dim) : 指定維上最大的數,返回tensor和下標
- t.max(tensor1, tensor2) : 比較兩個tensor,得到對應位置比較大的元素
1)比較函數
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類型
然后我們就能夠使用上面得到餓結果去獲取a>b的值:
a[a>b]
返回:
tensor([ 9., 12., 15.])
大小為:
torch.Size([3])
2)最值函數
#既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和一個數,可以使用clamp函數
#將a中各個位置的值和10比較,得到其中更大的值 t.clamp(a, min=10)
返回:
tensor([[10., 10., 10.], [10., 12., 15.]])
8.線性代數
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行后的對角線的元素;
l1 = t.tril(a, diagonal=1) #下三角,既除了下三角還多得到斜對角向上多一行的值 l1
返回:
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)轉置
矩陣的轉置會導致存儲空間不連續,需要調用其的.contiguous方法將其轉為連續
b = a.t()
b.is_contiguous() #查看空間是否連續
返回:
False
但是顯示b為:
tensor([[ 0., 9.], [ 3., 12.], [ 6., 15.]])
與調用.contiguous方法返回的值是相同的
b.contiguous().is_contiguous() #返回true
9.Tensor和Numpy
Numpy和Tensor共享內存
由於Numpy歷史悠久,支持豐富的操作,所以當遇見Tensor不支持的操作的時候,可以先將其轉成Numpy,處理完后再轉回tensor,其轉換開銷很小
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共享內存,一個改變,其他的也跟着改變 a[0,1] = 100 a
返回:
array([[ 1., 100., 1.], [ 1., 1., 1.]], dtype=float32)
查看b,c的值發現也是這個
10.自動廣播
- pytorch支持自動廣播法則 :快速執行向量化的同時不會占用額外的內存/顯存
法則定義:
- 讓所有輸入數組向其中shape最長的數組看棄,shape不足的數組在前面添加1
- 當輸入法則的某個維度的長度為1時,計算時沿此維度復制擴充成一樣的形狀
a = t.ones(3,2) #二維 b = t.zeros(2,3,1) #三維 a+b
返回:
tensor([[[1., 1.], [1., 1.], [1., 1.]], [[1., 1.], [1., 1.], [1., 1.]]])
自動廣播法則為:
1. 因為a比b少1維,所以在a前面補1,形狀變為(1,3,2)。等價於a.unsqueeze(0)
2.a和b的第一和三維形狀不一樣,進行擴展,將他們的形狀都變成(2,3,2)
- 也可以通過函數實現手動廣播,這樣更直觀,更不易出錯:
unsqueeze或view : 為數據某一維的形狀補1
expand或expand_as :重復數組,該操作不會復制數組,所以不會占用額外空間
⚠️repeat實現與expand相類似的功能,不使用它的原因是repeat會把相同數據復制多份,因此會占用額外的空間
既上面的例子可以寫成:
a.unsqueeze(0).expand(2,3,2) + b.expand(2,3,2)
11.內部結構
tensor分為頭信息區(Tensor)和存儲區(Storage)
信息區主要保存着tensor的形狀(size)、步長(stride)、數據類型(type)等信息,而真正的數據則保存成連續數組,存儲在存儲區
因為數據動輒成千上萬,因此信息區元素占用內存較少,主要內存占用取決於tensor中元素的數目,即存儲區的大小
一般來說,一個tensor有着與之相對應的storage,storage是在data之上封裝的接口,便於使用
不同的tensor的頭信息一般不同,但是可能使用相同的storage
生成a:
a = t.arange(0,6) a.storage()
⚠️將這里改成a = t.arange(0,6).float(),用來保證得到的值的類型為FloatTensor
這跟下面遇見的一個問題相關,可以看到下面了解一下,然后再跟着操作
所以你的下面內容的值的類型應該為FloatTensor類型,我的仍是LongTensor,因為我沒有改過來
返回:
0 1 2 3 4 5 [torch.LongStorage of size 6]
生成b:
b = a.view(2,3) b.storage()
返回:
0 1 2 3 4 5 [torch.LongStorage of size 6]
對比兩者內存地址:
#一個對象的id值可以看作她的內存空間
#a,b storage的內存地址一樣,即是同一個storage
id(a.storage()) == id(b.storage())
返回:
True
改變某個值查看是否共享內存:
#a改變,b也隨之改變,因為他們共享storage,即內存 a[1] = 100 b
返回:
tensor([[ 0, 100, 2], [ 3, 4, 5]])
生成c:
#c從a的后兩個元素取起 c = a[2:] c.storage()#指向相同
返回:
0 100 2 3 4 5 [torch.LongStorage of size 6]
查看其首元素內存地址:
c.data_ptr(), a.data_ptr() #data_ptr返回tensor首元素的內存地址
#從結果可以看出兩者的地址相差16
#因為c是從a第二個元素選起的,每個元素占8個字節,因為a的值的類型是int64
返回:
(140707162378192, 140707162378176)
因為查看后a的類型為int64:
a.dtype
返回:
torch.int64
更改c:
c[0] = -100 #a,c也共享內存空間,c[0]的內存地址對應的是a[2]的內存地址 a
返回:
tensor([ 0, 100, -100, 3, 4, 5])
使用storage來生成新tensor:
d = t.Tensor(c.storage())#這樣a,b,c,d共享同樣的內存空間 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.0 2.0 3.0 4.0 5.0 6.0 [torch.FloatStorage of size 6]
再運行就成功了:
d = t.Tensor(dtypea.storage())#這樣a,b,c,d共享同樣的內存空間 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的基礎上進行,即在a = t.arange(0,6)后面添加.float(),然后從頭執行了一遍
d = t.Tensor(c.storage())#這樣a,b,c,d共享同樣的內存空間 d[0] = 6666 b
返回:
tensor([[ 6.6660e+03, 1.0000e+02, -1.0000e+02], [ 3.0000e+00, 4.0000e+00, 5.0000e+00]])
判斷是否共享內存:
#因此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
步長信息:是有層次結構的步長
#獲得步長信息
b.stride(), e.stride()
返回:
((3, 1), (6, 2))
查看空間是否連續:
#查看其值的內存空間是否連續
#因為e只取得了storage中的部分值,因此其是不連續的
b.is_contiguous(), e.is_contiguous()
返回:
(True, False)
從上面的操作中我們可以看出絕大多數的操作並不修改tensor的數據,即存儲區的內容,只是修改了頭信息區的內容
這種做法更節省內存,同時提升了處理速度
但是我們可以看見e的操作導致其不連續,這時候可以調用tensor.contiguous()方法將他們變成連續的數據。該方法是復制數據到新的內存中,不再與原來的數據共享storage,如:
e.contiguous().is_contiguous() #返回True
生成f:
print(e.data_ptr()) f = e.contiguous() print(f.data_ptr()) #可見為f新分配了內存空間 print(f) print(f.storage())#內存空間中只有兩個值 print(f.size()) print(e.data_ptr()) #e指向的內存沒有改變 f.is_contiguous() #這里的f的內存空間是連續的
返回:
140707203003760 140707160267104 tensor([[6666., -100.]]) 6666.0 -100.0 [torch.FloatStorage of size 2] torch.Size([1, 2]) 140707203003760 Out[56]: True
是否為連續內存空間有什么影響?
比如當你想要使用.view()轉換tensor的形狀時,如果該tensor的內存空間不是連續的則會報錯:
k = t.arange(0,6).view(2,3).float().t()#進行轉置,轉置后的k內存是不連續的 k.is_contiguous() k.view(-1)
報錯:
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()之前調用.contiguous(),改后為:
k = t.arange(0,6).view(2,3).float().t()#進行轉置,轉置后的k內存是不連續的 k.is_contiguous() k.contiguous().view(-1)
成功返回:
tensor([0., 3., 1., 4., 2., 5.])
12.其他與tensor相關內容
1)持久化
Tensor的保存和加載十分簡單,使用t.save和t.load即可完成相應的功能
在save/load時可指定使用的pickle模塊,在load時還可以將CPU tensor映射到CPU或其他GPU上
if t.cuda.is_available(): a = a.cuda(1) #把a轉為GPU1上的tensor t.save(a, 'a.pth') #加載為b,存儲與GPU1上(因為保存時tensor就在GPU1上) b = t.load('a.pth') #加載為c,存儲於CPU c = t.load('a.pth', map_location = lambda storage, loc:storage) #加載為d,存儲於GPU0上 d = t.load('a.pth', map_location={'cuda:1':'cuda:0'})
2)向量化
向量化計算是一種特殊的並行計算方式,一般程序在同一時間只執行一個操作的方法,它可以在同一時間執行多個操作,通常是對不同的數據執行同樣的一個或一批指令,或者說把指令應用於一個數組/向量上
向量化可極大程度地提高科學運算的效率,用來替代python中的for循環
舉例說明:
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循環:
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
可見時間有近十倍的差距
⚠️有幾點需要注意:
- 大多數t.function都會有一個參數out=None,產生的結果將保存在out指定的tensor之中
- t.set_num_threads可以設置PyTorch進行CPU多線程並行計算時所占用的線程,用來限制PyTorch所占用的CPU數目
- t.set_printpotions可以用來設置打印tensor時的數值精度和格式
下面舉例說明:
a = t.arange(0,32769).short() #int16(-32768 ~ 32767) print(a.dtype) print(a[-1],a[-2])#16bit的IntTensor精度有限導致溢出 b = t.IntTensor() t.arange(0,32769,out=b)#32bit的LongTensor就不會導致溢出了 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]])
設置精度:
t.set_printoptions(precision=10)#將精度設為10 a
返回:
tensor([[ 0.2181526423, -1.1149182320, 1.1780972481], [ 1.0244952440, 0.9339445829, 1.6027815342]])
13.小試牛刀——線性回歸
線性回歸利用數理統計中的回歸分析來確定兩種或兩種以上變量之間相互依賴的定量關系,其表達式為y=wx +b +e,誤差e服從均值為0的正態分布。線性回歸的損失函數是:
利用隨機梯度下降法更新參數w,b來最小化損失函數,最終學習得到w,b的數值
導入需要的包:
import torch as t from matplotlib import pyplot as plt from IPython import display
生成訓練數據:
#設置隨機數種子,保證在不同計算機上運行時下面的輸出一致 t.manual_seed(1000) def get_fake_data(batch_size=8): '''產生隨機數據:y=x*2+3,加上了些噪音點''' x = t.rand(batch_size, 1)*20 #生成size為(batch_size, 1)的二維數組,並元素乘20 y = x * 2 + (1+t.rand(batch_size, 1))*3 #用於生成噪音 return x,y
測試看看會生成什么樣的數據:
#來看看產生的x-y分布,輸出如圖所示 x, y = get_fake_data() plt.scatter(x.squeeze().numpy(), y.squeeze().numpy())
圖示為:
生成參數:
#隨機初始化參數 w = t.rand(1,1) b = t.zeros(1,1)
實現:
lr = 0.001 #學習率 for ii in range(2000): #2000次迭代 x, y = get_fake_data() #生成數據 #前向傳播forward,計算loss y_pred = x.mm(w) + b.expand_as(y) loss = 0.5*(y_pred - y)**2 #均方誤差就loss loss = loss.sum() #backward:手動計算梯度 dloss = 1 dy_pred = dloss *(y_pred - y) dw = x.t().mm(dy_pred) db = dy_pred.sum() #更新參數 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()) #預測 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)
上面得到的是學習到的w,b的值,看下面的圖可以看見直線和數據很好地實現了擬合
圖示為: