跟着Dive-into-DL-PyTorch.pdf從頭開始學pytorch,夯實基礎.
Tensor創建
創建未初始化的tensor
import torch
x = torch.empty(5,3)
print(x)
輸出
tensor([[ 2.0909e+21, 3.0638e-41, -2.4612e-30],
[ 4.5650e-41, 3.0638e-41, 1.7753e+28],
[ 4.4339e+27, 1.3848e-14, 6.8801e+16],
[ 1.8370e+25, 1.4603e-19, 6.8794e+11],
[ 2.7253e+20, 3.0866e+29, 1.5835e-43]])
創建隨機初始化的tensor
x = torch.rand(5,3)
print(x)
輸出
tensor([[0.7302, 0.0805, 0.9499],
[0.9323, 0.2995, 0.2943],
[0.7428, 0.8312, 0.6465],
[0.7843, 0.7300, 0.7509],
[0.4965, 0.1318, 0.9063]])
創建全0的tensor,指定類型為long
x = torch.zeros(5,3,dtype=torch.long)
輸出
tensor([[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
指定數據創建
x = torch.tensor([5.5,3])
print(x)
輸出
tensor([5.5000, 3.0000])
利用已有tensor來創建,新創建的tensor和已有tensor具有相同數據類型,除非手動指定.
print(x.shape,x.dtype)
x = x.new_ones(5, 3, dtype=torch.float64) # 返回的tensor默認具有相同的torch.dtype和torch.device
print(x)
輸出
torch.Size([2]) torch.float32
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]], dtype=torch.float64)
x=torch.rand_like(x)
print(x)
輸出
tensor([[0.0646, 0.4401, 0.3052],
[0.7657, 0.7720, 0.5770],
[0.2951, 0.4179, 0.6013],
[0.2007, 0.0473, 0.7378],
[0.1789, 0.1498, 0.1430]], dtype=torch.float64)
還有很多創建tensor的方法
| 函數 | 功能 |
|---|---|
| Tensor(*sizes) | 基礎構造函數 |
| tensor(data,) | 類似np.array的構造函數 |
| ones(*sizes) | 全1Tensor |
| zeros(*sizes) | 全0Tensor |
| eye(*sizes) | 對角線為1,其他為0 |
| arange(s,e,step | 從s到e,步長為step |
| linspace(s,e,steps) | 從s到e,均勻切分成steps份 |
| rand/randn(*sizes) | 均勻/標准分布 |
| normal(mean,std)/uniform(from,to) | 正態分布/均勻分布 |
| randperm(m) | 隨機排列 |
這些創建方法都可以在創建的時候指定數據類型dtype和存放device(cpu/gpu)。
各種操作
- 算數操作
- 索引
- 改變形狀
- 線性代數
算數操作
在PyTorch中,同一種操作可能有很多種形式,下面用加法作為例子。
- 加法形式一
y = torch.rand(5, 3) print(x + y) - 加法形式二
還可指定輸出:print(torch.add(x, y))result = torch.empty(5, 3) torch.add(x, y, out=result) print(result) - 加法形式三、inplace
# adds x to y y.add_(x) print(y)注:PyTorch操作inplace版本都有后綴"_", 例如
x.copy_(y), x.t_()
索引
我們還可以使用類似NumPy的索引操作來訪問Tensor的一部分,需要注意的是:索引出來的結果與原數據共享內存,也即修改一個,另一個會跟着修改。
y = x[0, :]
y += 1
print(y)
print(x[0, :]) # 源tensor也被改了
輸出:
tensor([1.6035, 1.8110, 0.9549])
tensor([1.6035, 1.8110, 0.9549])
除了常用的索引選擇數據之外,PyTorch還提供了一些高級的選擇函數:
| 函數 | 功能 |
|---|---|
| index_select(input, dim, index) | 在指定維度dim上選取,比如選取某些行、某些列 |
| masked_select(input, mask) | 例子如上,a[a>0],使用ByteTensor進行選取 |
| non_zero(input) | 非0元素的下標 |
| gather(input, dim, index) | 根據index,在dim維度上選取數據,輸出的size與index一樣 |
用view()來改變Tensor的形狀:
y = x.view(15)
z = x.view(-1, 5) # -1所指的維度可以根據其他維度的值推出來
print(x.size(), y.size(), z.size())
輸出:
torch.Size([5, 3]) torch.Size([15]) torch.Size([3, 5])
注意view()返回的新tensor與源tensor共享內存(其實是同一個tensor),也即更改其中的一個,另外一個也會跟着改變。(顧名思義,view僅僅是改變了對這個張量的觀察角度)
x += 1
print(x)
print(y) # 也加了1
輸出:
tensor([[1.6035, 1.8110, 0.9549],
[1.8797, 2.0482, 0.9555],
[0.2771, 3.8663, 0.4345],
[1.1604, 0.9746, 2.0739],
[3.2628, 0.0825, 0.7749]])
tensor([1.6035, 1.8110, 0.9549, 1.8797, 2.0482, 0.9555, 0.2771, 3.8663, 0.4345,
1.1604, 0.9746, 2.0739, 3.2628, 0.0825, 0.7749])
所以如果我們想返回一個真正新的副本(即不共享內存)該怎么辦呢?Pytorch還提供了一個reshape()可以改變形狀,但是此函數並不能保證返回的是其拷貝,所以不推薦使用。推薦先用clone創造一個副本然后再使用view。參考此處
x_cp = x.clone().view(15)
x -= 1
print(x)
print(x_cp)
輸出:
tensor([[ 0.6035, 0.8110, -0.0451],
[ 0.8797, 1.0482, -0.0445],
[-0.7229, 2.8663, -0.5655],
[ 0.1604, -0.0254, 1.0739],
[ 2.2628, -0.9175, -0.2251]])
tensor([1.6035, 1.8110, 0.9549, 1.8797, 2.0482, 0.9555, 0.2771, 3.8663, 0.4345,
1.1604, 0.9746, 2.0739, 3.2628, 0.0825, 0.7749])
使用
clone還有一個好處是會被記錄在計算圖中,即梯度回傳到副本時也會傳到源Tensor。
另外一個常用的函數就是item(), 它可以將一個標量Tensor轉換成一個Python number:
x = torch.randn(1)
print(x)
print(x.item())
輸出:
tensor([2.3466])
2.3466382026672363
線性代數
另外,PyTorch還支持一些線性函數,這里提一下,免得用起來的時候自己造輪子,具體用法參考官方文檔。如下表所示:
| 函數 | 功能 |
|---|---|
| trace | 對角線元素之和(矩陣的跡) |
| diag | 對角線元素 |
| triu/tril | 矩陣的上三角/下三角,可指定偏移量 |
| mm/bmm | 矩陣乘法,batch的矩陣乘法 |
| addmm/addbmm/addmv/addr/badbmm.. | 矩陣運算 |
| t | 轉置 |
| dot/cross | 內積/外積 |
| inverse | 求逆矩陣 |
| svd | 奇異值分解 |
PyTorch中的Tensor支持超過一百種操作,包括轉置、索引、切片、數學運算、線性代數、隨機數等等,可參考官方文檔。
2.2.3 廣播機制
前面我們看到如何對兩個形狀相同的Tensor做按元素運算。當對兩個形狀不同的Tensor按元素運算時,可能會觸發廣播(broadcasting)機制:先適當復制元素使這兩個Tensor形狀相同后再按元素運算。例如:
x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)
輸出:
tensor([[1, 2]])
tensor([[1],
[2],
[3]])
tensor([[2, 3],
[3, 4],
[4, 5]])
由於x和y分別是1行2列和3行1列的矩陣,如果要計算x + y,那么x中第一行的2個元素被廣播(復制)到了第二行和第三行,而y中第一列的3個元素被廣播(復制)到了第二列。如此,就可以對2個3行2列的矩陣按元素相加。
2.2.4 運算的內存開銷
前面說了,索引、view是不會開辟新內存的,而像y = x + y這樣的運算是會新開內存的,然后將y指向新內存。為了演示這一點,我們可以使用Python自帶的id函數:如果兩個實例的ID一致,那么它們所對應的內存地址相同;反之則不同。
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y = y + x
print(id(y) == id_before) # False
如果想指定結果到原來的y的內存,我們可以使用前面介紹的索引來進行替換操作。在下面的例子中,我們把x + y的結果通過[:]寫進y對應的內存中。
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y[:] = y + x
print(id(y) == id_before) # True
我們還可以使用運算符全名函數中的out參數或者自加運算符+=(也即add_())達到上述效果,例如torch.add(x, y, out=y)和y += x(y.add_(x))。
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
torch.add(x, y, out=y) # y += x, y.add_(x)
print(id(y) == id_before) # True
2.2.5 Tensor和NumPy相互轉換
我們很容易用numpy()和from_numpy()將Tensor和NumPy中的數組相互轉換。但是需要注意的一點是:
這兩個函數所產生的的Tensor和NumPy中的數組共享相同的內存(所以他們之間的轉換很快),改變其中一個時另一個也會改變!!!
還有一個常用的將NumPy中的array轉換成
Tensor的方法就是torch.tensor(), 需要注意的是,此方法總是會進行數據拷貝(就會消耗更多的時間和空間),所以返回的Tensor和原來的數據不再共享內存。
Tensor轉NumPy
使用numpy()將Tensor轉換成NumPy數組:
a = torch.ones(5)
b = a.numpy()
print(a, b)
a += 1
print(a, b)
b += 1
print(a, b)
輸出:
tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.]
tensor([2., 2., 2., 2., 2.]) [2. 2. 2. 2. 2.]
tensor([3., 3., 3., 3., 3.]) [3. 3. 3. 3. 3.]
NumPy數組轉Tensor
使用from_numpy()將NumPy數組轉換成Tensor:
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
print(a, b)
a += 1
print(a, b)
b += 1
print(a, b)
輸出:
[1. 1. 1. 1. 1.] tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
[2. 2. 2. 2. 2.] tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
[3. 3. 3. 3. 3.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)
所有在CPU上的Tensor(除了CharTensor)都支持與NumPy數組相互轉換。
此外上面提到還有一個常用的方法就是直接用torch.tensor()將NumPy數組轉換成Tensor,需要注意的是該方法總是會進行數據拷貝,返回的Tensor和原來的數據不再共享內存。
c = torch.tensor(a)
a += 1
print(a, c)
輸出
[4. 4. 4. 4. 4.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)
2.2.6 Tensor on GPU
用方法to()可以將Tensor在CPU和GPU(需要硬件支持)之間相互移動。
# 以下代碼只有在PyTorch GPU版本上才會執行
if torch.cuda.is_available():
device = torch.device("cuda") # GPU
y = torch.ones_like(x, device=device) # 直接創建一個在GPU上的Tensor
x = x.to(device) # 等價於 .to("cuda")
z = x + y
print(z)
print(z.to("cpu", torch.double)) # to()還可以同時更改數據類型
輸出
tensor([2, 3], device='cuda:0')
tensor([2., 3.], dtype=torch.float64)
