本篇我們介紹張量的結構操作。
張量結構操作主要包括: 張量建立、索引切片、維度變換、合併分割。
1.建立張量
張量建立的許多方法和numpy中建立array的方法很像。
import numpy as np
import torch
a = torch.tensor([1, 2, 3], dtype=torch.float)
print(a)
"""
tensor([1., 2., 3.])
"""
b = torch.arange(1, 10, step=2)
print(b)
"""
tensor([1, 3, 5, 7, 9])
"""
c = torch.linspace(0.0, 2*3.14, 10)
print(c)
"""
tensor([0.0000, 0.6978, 1.3956, 2.0933, 2.7911, 3.4889, 4.1867, 4.8844, 5.5822,
6.2800])
"""
d = torch.zeros((3, 3))
print(d)
"""
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
"""
a = torch.ones((3, 3), dtype=torch.int)
b = torch.zeros_like(a, dtype=torch.float)
print(a)
print(b)
"""
tensor([[1, 1, 1],
[1, 1, 1],
[1, 1, 1]], dtype=torch.int32)
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
"""
torch.fill_(b, 5)
print(b)
"""
tensor([[5., 5., 5.],
[5., 5., 5.],
[5., 5., 5.]])
"""
# 均勻隨機分佈
torch.manual_seed(0)
minval, maxval = 0, 10
a = minval + (maxval - minval) * torch.rand(5)
print(a)
"""
tensor([4.9626, 7.6822, 0.8848, 1.3203, 3.0742])
"""
# 正態分佈
b = torch.normal(mean=torch.zeros(3, 3), std=torch.ones(3, 3))
print(b)
"""
tensor([[ 0.5507, 0.2704, 0.6472],
[ 0.2490, -0.3354, 0.4564],
[-0.6255, 0.4539, -1.3740]])
"""
# 整數隨機排列
d = torch.randperm(20)
print(d)
"""
tensor([ 1, 11, 19, 14, 12, 6, 5, 9, 8, 18, 15, 4, 2, 3, 7, 13, 0, 10,
16, 17])
"""
# 特殊矩陣
I = torch.eye(3, 3)
print(I)
t = torch.diag(torch.tensor([1, 2, 3]))
print(t)
"""
tensor([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
tensor([[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
"""
2.索引切片
張量的索引切片方式和numpy幾乎是一樣的。切片時支援預設引數和省略號。
可以透過索引和切片對部分元素進行修改。
此外,對於不規則的切片提取,可以使用torch.index_select, torch.masked_select, torch.take
如果要透過修改張量的某些元素得到新的張量,可以使用torch.where, torch.masked_fill, torch.index_fill
# 均勻隨機分佈
torch.manual_seed(0)
minval, maxval = 0, 10
t = torch.floor(minval + (maxval - minval) * torch.rand(5, 5)).int()
print(t)
"""
tensor([[4, 7, 0, 1, 3],
[6, 4, 8, 4, 6],
[3, 4, 0, 1, 2],
[5, 6, 8, 1, 2],
[6, 9, 3, 8, 4]], dtype=torch.int32)
"""
# 第0行
print(t[0])
"""
tensor([4, 7, 0, 1, 3], dtype=torch.int32)
"""
# 倒數第一行
print(t[-1])
"""
tensor([6, 9, 3, 8, 4], dtype=torch.int32)
"""
# 第一行第3列
print(t[1, 3])
print(t[1][3])
"""
tensor(4, dtype=torch.int32)
tensor(4, dtype=torch.int32)
"""
# 第一行至第三行
print(t[1:4, :])
"""
tensor([[6, 4, 8, 4, 6],
[3, 4, 0, 1, 2],
[5, 6, 8, 1, 2]], dtype=torch.int32)
"""
# 第1行至最後一行,第0列到最後一列每隔兩列取一列
print(t[1:4, :4:2])
"""
tensor([[6, 8],
[3, 0],
[5, 8]], dtype=torch.int32)
"""
# 可以使用索引和切片修改部分元素
x = torch.Tensor([[1, 2], [3, 4]])
x.data[1, :] = torch.tensor([0.0, 0.0])
x
"""
tensor([[1., 2.],
[0., 0.]])
"""
a = torch.arange(27).view(3, 3, 3)
print(a)
"""
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]]])
"""
# 省略號可以表示多個冒號
print(a[..., 1])
"""
tensor([[ 1, 4, 7],
[10, 13, 16],
[19, 22, 25]])
"""
以上切片方式相對規則,對於不規則切片的提取,可以使用torch.index_select, torch.take, torch.gather, torch.masked_select
考慮班級成績冊的例子,有四個班級,每個班級5個學生, 每個學生7門科目成績,可以用一個457的張量來表示
minval = 0
maxval = 100
scores = torch.floor(minval + (maxval - minval) * torch.rand(4, 5, 7)).int()
print(scores)
"""
tensor([[[55, 95, 3, 18, 37, 30, 93],
[17, 26, 15, 3, 20, 92, 72],
[74, 52, 24, 58, 3, 13, 24],
[81, 79, 27, 48, 81, 99, 69],
[56, 83, 20, 59, 11, 15, 24]],
[[72, 70, 20, 65, 77, 43, 51],
[61, 81, 98, 11, 31, 69, 91],
[93, 94, 59, 6, 54, 18, 3],
[94, 88, 0, 59, 41, 41, 27],
[69, 20, 68, 75, 85, 68, 0]],
[[17, 74, 60, 10, 21, 97, 83],
[28, 37, 2, 49, 12, 11, 47],
[57, 29, 79, 19, 95, 84, 7],
[37, 52, 57, 61, 69, 52, 25],
[73, 2, 20, 37, 25, 32, 9]],
[[39, 60, 17, 47, 85, 44, 51],
[45, 60, 81, 97, 81, 97, 46],
[ 5, 26, 84, 49, 25, 11, 3],
[ 7, 39, 77, 77, 1, 81, 10],
[39, 29, 40, 40, 5, 6, 42]]], dtype=torch.int32)
"""
# 抽取每個班級第0個學生,第2個學生,第四個學生的全部成績
torch.index_select(scores, dim=1, index=torch.tensor([0, 2, 4]))
"""
tensor([[[55, 95, 3, 18, 37, 30, 93],
[74, 52, 24, 58, 3, 13, 24],
[56, 83, 20, 59, 11, 15, 24]],
[[72, 70, 20, 65, 77, 43, 51],
[93, 94, 59, 6, 54, 18, 3],
[69, 20, 68, 75, 85, 68, 0]],
[[17, 74, 60, 10, 21, 97, 83],
[57, 29, 79, 19, 95, 84, 7],
[73, 2, 20, 37, 25, 32, 9]],
[[39, 60, 17, 47, 85, 44, 51],
[ 5, 26, 84, 49, 25, 11, 3],
[39, 29, 40, 40, 5, 6, 42]]], dtype=torch.int32)
"""
# 抽取每個班級第0個學生,第2個學生,第4個學生的第一門課程,第三門課程,第6門課程成績
q = torch.index_select(torch.index_select(scores, dim=1, index=torch.tensor([0, 2, 4])), dim=2, index=torch.tensor([1, 3, 6]))
print(q)
"""
tensor([[[95, 18, 93],
[52, 58, 24],
[83, 59, 24]],
[[70, 65, 51],
[94, 6, 3],
[20, 75, 0]],
[[74, 10, 83],
[29, 19, 7],
[ 2, 37, 9]],
[[60, 47, 51],
[26, 49, 3],
[29, 40, 42]]], dtype=torch.int32)
"""
# 抽取第0個班級第0個學生的第0門課程,第2個班級的第3個學生的第一門課程,第3個班級的第四個學生的第6門課程
# take將輸入看成一維陣列,輸出和index同形狀
s = torch.take(scores, torch.tensor([0*5*7+0, 2*5*7+3*7+1, 3*5*7+4*7+6]))
print(s)
"""
tensor([55, 52, 42], dtype=torch.int32)
"""
# 抽取分數大於等於80的分數(布林索引)
# 結果是1維張量
g = torch.masked_select(scores, scores>=80)
print(g)
"""
tensor([95, 93, 92, 81, 81, 99, 83, 81, 98, 91, 93, 94, 94, 88, 85, 97, 83, 95,
84, 85, 81, 97, 81, 97, 84, 81], dtype=torch.int32)
"""
以上這些方法僅能提取張量的部分元素值,但不能更改張量的部分元素值得到新的張量。
如果要透過修改張量的部分元素值得到新的張量,可以使用torch.where, torch.index_fill, torch.masked_fill
torch.where可以理解為if的張量版本。
torch.index_fill的選取元素邏輯和torch.index_select相同
torch.masked_fill的選取元素邏輯和torch.masked_select相同
# 如果分數大於60分,賦值成1,否則賦值成0
ifpass = torch.where(scores>60, torch.tensor(1), torch.tensor(0))
print(ifpass)
"""
tensor([[[0, 1, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 1, 1],
[1, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 1, 1, 1],
[0, 1, 0, 0, 0, 0, 0]],
[[1, 1, 0, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 1, 1],
[1, 1, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0, 0],
[1, 0, 1, 1, 1, 1, 0]],
[[0, 1, 0, 0, 0, 1, 1],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 1, 1, 0],
[0, 0, 0, 1, 1, 0, 0],
[1, 0, 0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0],
[0, 0, 1, 0, 0, 0, 0],
[0, 0, 1, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0]]])
"""
# 將每個班級第0個學生,第2個學生,第4個學生的全部成績賦值成滿分
torch.index_fill(scores, dim=1, index=torch.tensor([0, 2, 4]), value=100)
# 等價於scores.index_fill(dim = 1,index = torch.tensor([0,2,4]),value = 100)
"""
tensor([[[100, 100, 100, 100, 100, 100, 100],
[ 17, 26, 15, 3, 20, 92, 72],
[100, 100, 100, 100, 100, 100, 100],
[ 81, 79, 27, 48, 81, 99, 69],
[100, 100, 100, 100, 100, 100, 100]],
[[100, 100, 100, 100, 100, 100, 100],
[ 61, 81, 98, 11, 31, 69, 91],
[100, 100, 100, 100, 100, 100, 100],
[ 94, 88, 0, 59, 41, 41, 27],
[100, 100, 100, 100, 100, 100, 100]],
[[100, 100, 100, 100, 100, 100, 100],
[ 28, 37, 2, 49, 12, 11, 47],
[100, 100, 100, 100, 100, 100, 100],
[ 37, 52, 57, 61, 69, 52, 25],
[100, 100, 100, 100, 100, 100, 100]],
[[100, 100, 100, 100, 100, 100, 100],
[ 45, 60, 81, 97, 81, 97, 46],
[100, 100, 100, 100, 100, 100, 100],
[ 7, 39, 77, 77, 1, 81, 10],
[100, 100, 100, 100, 100, 100, 100]]], dtype=torch.int32)
"""
# 將分數小於60分的分數賦值成60分
b = torch.masked_fill(scores, scores<60, 60)
b
"""
tensor([[[60, 95, 60, 60, 60, 60, 93],
[60, 60, 60, 60, 60, 92, 72],
[74, 60, 60, 60, 60, 60, 60],
[81, 79, 60, 60, 81, 99, 69],
[60, 83, 60, 60, 60, 60, 60]],
[[72, 70, 60, 65, 77, 60, 60],
[61, 81, 98, 60, 60, 69, 91],
[93, 94, 60, 60, 60, 60, 60],
[94, 88, 60, 60, 60, 60, 60],
[69, 60, 68, 75, 85, 68, 60]],
[[60, 74, 60, 60, 60, 97, 83],
[60, 60, 60, 60, 60, 60, 60],
[60, 60, 79, 60, 95, 84, 60],
[60, 60, 60, 61, 69, 60, 60],
[73, 60, 60, 60, 60, 60, 60]],
[[60, 60, 60, 60, 85, 60, 60],
[60, 60, 81, 97, 81, 97, 60],
[60, 60, 84, 60, 60, 60, 60],
[60, 60, 77, 77, 60, 81, 60],
[60, 60, 60, 60, 60, 60, 60]]], dtype=torch.int32)
"""
3.維度變換
維度變換相關哈數主要有torch.reshape(或者呼叫張量的view方法),torch.squeeze, torch.unsqueeze,torch.transpose
torch.reshape可以改變張量的形狀
torch.squeeze可以減少維度
torch.unsqueeze可以增加維度
torch.transpose/torch.permute可以交換維度
# 張量的view方法有時候會呼叫四百,可以使用reshape方法
torch.manual_seed(0)
minval, maxval = 0, 255
a = (minval + (maxval - minval) * torch.rand([1, 3, 3, 2])).int()
print(a.shape)
print(a)
"""
torch.Size([1, 3, 3, 2])
tensor([[[[126, 195],
[ 22, 33],
[ 78, 161]],
[[124, 228],
[116, 161],
[ 88, 102]],
[[ 5, 43],
[ 74, 132],
[177, 204]]]], dtype=torch.int32)
"""
# 改成(3, 6)形狀的張量
b = a.view(3, 6)
print(b.shape)
print(b)
"""
torch.Size([3, 6])
tensor([[126, 195, 22, 33, 78, 161],
[124, 228, 116, 161, 88, 102],
[ 5, 43, 74, 132, 177, 204]], dtype=torch.int32)
"""
# 改回[1, 3, 3, 2]形狀的張量
c = torch.reshape(b, [1, 3, 3, 2])
print(c)
"""
tensor([[[[126, 195],
[ 22, 33],
[ 78, 161]],
[[124, 228],
[116, 161],
[ 88, 102]],
[[ 5, 43],
[ 74, 132],
[177, 204]]]], dtype=torch.int32)
"""
如果張量在某個維度上只有一個元素,利用torch.squeeze可以消除這個維度
torch.unsqueeze的作用和torch.squeeze的作用相反
a = torch.tensor([[1.0, 2.0]])
s = torch.squeeze(a)
print(a)
print(s)
print(a.shape)
print(s.shape)
"""
tensor([[1., 2.]])
tensor([1., 2.])
torch.Size([1, 2])
torch.Size([2])
"""
# 在第0維度插入長度為1的一個維度
d = torch.unsqueeze(s, axis=0)
print(s)
print(d)
print(s.shape)
print(d.shape)
"""
tensor([1., 2.])
tensor([[1., 2.]])
torch.Size([2])
torch.Size([1, 2])
"""
torch.transpose可以交換張量的維度,torch.transpose常用於圖片儲存格式的變換上
如果是二維的矩陣,通常會呼叫矩陣的轉置方法matrix.t(),等價於torch.transpose(matrix, 0, 1)
minval = 0
maxval = 255
data = torch.floor(minval + (maxval - minval) * torch.rand([100, 256, 256, 4])).int()
print(data.shape)
# 轉換成Pytorch預設的圖片格式Batch,Channel,Height,Width
data_t = torch.transpose(torch.transpose(data, 1, 2), 1, 3)
print(data_t.shape)
data_p = torch.permute(data, [0, 3, 1, 2]) # 對維度的順序重新編排
data_p.shape
"""
torch.Size([100, 256, 256, 4])
torch.Size([100, 4, 256, 256])
torch.Size([100, 4, 256, 256])
"""
matrix = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(matrix)
print(matrix.t())
"""
tensor([[1, 2, 3],
[4, 5, 6]])
tensor([[1, 4],
[2, 5],
[3, 6]])
"""
4.合併分割
可以用torch.cat方法和torch.stack方法將多個張量合併,可以用torch.split方法把一個張量分割成多個張量
torch.cat和torch.stack有略微的區別,torch.cat是連線,不會增加維度,而torch.stack是堆疊,會增加維度
a = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
b = torch.tensor([[5.0, 6.0], [7.0, 8.0]])
c = torch.tensor([[9.0, 10.0], [11.0, 12.0]])
abc_cat = torch.cat([a, b, c], dim=0)
print(abc_cat.shape)
print(abc_cat)
"""
torch.Size([6, 2])
tensor([[ 1., 2.],
[ 3., 4.],
[ 5., 6.],
[ 7., 8.],
[ 9., 10.],
[11., 12.]])
"""
abc_stack = torch.stack([a, b, c], axis=0) # torch中的dim和aixs引數名可以混用
print(abc_stack.shape)
print(abc_stack)
"""
torch.Size([3, 2, 2])
tensor([[[ 1., 2.],
[ 3., 4.]],
[[ 5., 6.],
[ 7., 8.]],
[[ 9., 10.],
[11., 12.]]])
"""
torch.cat([a, b, c], axis=1)
"""
tensor([[ 1., 2., 5., 6., 9., 10.],
[ 3., 4., 7., 8., 11., 12.]])
"""
torch.stack([a, b, c], axis=1)
"""
tensor([[[ 1., 2.],
[ 5., 6.],
[ 9., 10.]],
[[ 3., 4.],
[ 7., 8.],
[11., 12.]]])
"""
torch.split是torch.cat的逆運算,可以指定分割份數平均分割,也可以透過指定每份的記錄數量進行分割
print(abc_cat)
a, b, c = torch.split(abc_cat, split_size_or_sections=2, dim=0) # 每份2個進行分割
print(a)
print(b)
print(c)
"""
tensor([[ 1., 2.],
[ 3., 4.],
[ 5., 6.],
[ 7., 8.],
[ 9., 10.],
[11., 12.]])
tensor([[1., 2.],
[3., 4.]])
tensor([[5., 6.],
[7., 8.]])
tensor([[ 9., 10.],
[11., 12.]])
"""
print(abc_cat)
p, q, r = torch.split(abc_cat, split_size_or_sections=[4, 1, 1], dim=0) # 每份分別為[4, 1, 1]
print(p)
print(q)
print(r)
"""
tensor([[ 1., 2.],
[ 3., 4.],
[ 5., 6.],
[ 7., 8.],
[ 9., 10.],
[11., 12.]])
tensor([[1., 2.],
[3., 4.],
[5., 6.],
[7., 8.]])
tensor([[ 9., 10.]])
tensor([[11., 12.]])
"""