本文以pytorch1.10进行解读:torch — PyTorch 1.10 documentation
文本的操作在github上都有Shirley-Xie/pytorch_exercise · GitHub,且有运行结果。
1 . 张量的合并和切分
可以用torch.cat方法和torch.stack方法将多个张量合并。 torch.cat和torch.stack有略微的区别,torch.cat是连接,不会增加维度,而torch.stack是堆叠,会增加维度。
1.1 合并 cat和stack
torch.cat(tensors, dim=0,*, out=None)
将张量按维度dim进行拼接,张量拼接维度要相同, torch.cat()处理之后维度不会变。
torch.stack(tensors, dim=0,*, out=None)
在新创建的维度dim上进行拼接,需要同样维度的张量。torch.stack()处理之后会增加一个维度。
cat()拼接只需要拼接维度相同,但是stack()需要张量维度相同,要求更为严格。
# 张量的拼接
t1 = torch.zeros((2, 3))
t2 = torch.ones((2, 2))
t3 = torch.ones((2, 3))
# print(t1,'\n',t3)
# t_1 = torch.cat([t1, t2]) # 列拼接,报错,因为维度不一样
# t_0 = torch.stack([t1, t2], dim=1)
t_0 = torch.cat([t1, t3], dim=1)# 换成了stack拼接 报错
t_1 = torch.stack([t1, t3], dim=1)
# print(torch.cat([t1, t2], dim=1))
print('t_0:',t_0,'\n','t_1:', t_1)
print('cat:',t_0.shape, t_0.dim())
print('stack:', t_1.shape, t_1.dim())
结果:
t_0: tensor([[0., 0., 0., 1., 1., 1.],
[0., 0., 0., 1., 1., 1.]])
t_1: tensor([[[0., 0., 0.],
[1., 1., 1.]],
[[0., 0., 0.],
[1., 1., 1.]]])
cat: torch.Size([2, 6]) 2
stack: torch.Size([2, 2, 3]) 3
在t1,t2,t3三个向量中任意两个向量可以进行特定维度的cat。只有相同维度的t1,t3可以进行stack,同时维度上增一级。
1.2 切分 spilt 和chunk
spilt按照指定的量切,chunk按照给定的份数切。
torch.spilt(tensor, split_size_or_sections, dim=0)
将张量按维度dim切分,指定切分的长度, split_size_or_sections为int时表示每一份的长度,不能被整除则剩下的为一块; 为list时,按list数值切分,list的数值总和应该和切分维度一致,否则报错。
import torch
x = torch.rand(4,8,6)
print(torch.split(torch.arange(18).reshape(6,3),2))
a,b,c = torch.split(x,[2,3,3],dim=1) # 2,3,3是份数,在dim上总共是8份
print(a.shape,b.shape,c.shape)
# torch.split(x,[2,1,4],dim=1) #2+1+3 等于8,报错
结果:
torch.Size([4, 2, 6]) torch.Size([4, 3, 6]) torch.Size([4, 3, 6])
两者都为int的时候比较,只有spilt()有list操作,为list只有维度总数不对时报错。
torch.chunk(input, chunks, dim=0)
将张量按维度dim进行平均切分, 返回值是张量列表,注意,如果不能整除, 最后一份张量小于其他张量。 chunks代表要切分的维度。
a = torch.arange(11).chunk(6)
b = torch.arange(12).chunk(6)
c = torch.arange(13).chunk(6)
print(a,'\n', b,'\n', c)
不可取余的计算,11除以6=1余5,取2。13除以6=2余1,取大为3。 结果行数比6小。
(tensor([0, 1]), tensor([2, 3]), tensor([4, 5]), tensor([6, 7]), tensor([8, 9]), tensor([10]))
(tensor([0, 1]), tensor([2, 3]), tensor([4, 5]), tensor([6, 7]), tensor([8, 9]), tensor([10, 11]))
(tensor([0, 1, 2]), tensor([3, 4, 5]), tensor([6, 7, 8]), tensor([ 9, 10, 11]), tensor([12])
2. 索引切片
2.1 有规则的切片,切片方式和numpy几乎是一样的。切片时支持缺省参数和省略号。可以通过索引和切片对部分元素进行修改。
2.2 对于不规则的切片提取,可以使用torch.index_select, torch.masked_select, torch.take。
2.3 如果要通过修改张量的某些元素得到新的张量,可以使用torch.where,torch.masked_fill,
torch.index_fill。
2.1 规则索引切片提取和更改
索引x1:x2:x3代表[x1,x2) step=x3,在不同的位置代表切相应位置。仅用 :支持缺省参数。
t = torch.arange(1, 21).reshape(5,4)
print(t[1,2],t[1][2]) #第1行第2列,两种表达是一样的
print(t, '\n 取第一行\n',t[0])
print('\n 取第2行到第4行\n',t[1:4,:])
print('\n 取第2行到第4行的,第0列到最后一列每隔两列取一列\n', t[1:4,0:4:2])
# 也就是说0:4:2,[0,4)step=2
结果:
tensor(7) tensor(7)
tensor([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12],
[13, 14, 15, 16],
[17, 18, 19, 20]])
取第一行
tensor([1, 2, 3, 4])
取第2行到第4行
tensor([[ 5, 6, 7, 8],
[ 9, 10, 11, 12],
[13, 14, 15, 16]])
取第2行到第4行的,第0列到最后一列每隔两列取一列
tensor([[ 5, 7],
[ 9, 11],
[13, 15]])
支持缺省略号
#可以使用索引和切片修改部分元素
x = torch.Tensor([[1,2],[3,4]])
x.data[1,:] = torch.tensor([0.0,0.0])
#省略号可以表示多个冒号
print(x, '\n',x[...,1])
"""
tensor([[1., 2.],
[0., 0.]])
tensor([2., 0.])
"""
2.2 不规则索引切片提取
三种方法:
torch.index_select(input, dim, index, out=None) 指定索引选择
torch.masked_select(input, mask, out=None)按照mask中的True进行索引返回一维向量
torch.take(input, index, ) 索引展为一维计算
举例说明操作:
考虑班级成绩册的例子,有4个班级,每个班级5个学生,每个学生7门科目成绩。可以用一个4×5×7的张量来表示。
minval=0
maxval=100
scores = torch.floor(minval + (maxval-minval)*torch.rand([4,5,7])).int()
print(scores);
"""
结果:
tensor([[[48, 51, 24, 62, 75, 16, 16],
[80, 45, 80, 85, 95, 43, 90],
[79, 51, 39, 71, 73, 52, 66],
[ 4, 15, 64, 85, 58, 36, 43],
[48, 22, 91, 92, 80, 95, 44]],
[[58, 0, 45, 18, 41, 81, 34],
[23, 44, 78, 54, 35, 18, 54],
[35, 0, 83, 12, 85, 43, 25],
[34, 28, 50, 13, 93, 12, 31],
[99, 33, 31, 85, 86, 4, 6]],
[[41, 72, 92, 44, 11, 30, 58],
[66, 90, 45, 3, 20, 48, 57],
[50, 56, 37, 70, 70, 21, 17],
[41, 50, 74, 83, 82, 21, 17],
[68, 55, 16, 71, 96, 45, 21]],
[[51, 97, 20, 32, 3, 93, 26],
[ 8, 37, 77, 58, 28, 82, 27],
[81, 10, 72, 32, 45, 71, 86],
[79, 43, 95, 34, 53, 29, 13],
[13, 61, 48, 96, 99, 73, 73]]], dtype=torch.int32)
"""
#抽取每个班级第0个学生,第2个学生,第4个学生的全部成绩
torch.index_select(scores,dim = 1,index = torch.tensor([0,2,4]))
"""
结果:
tensor([[[48, 51, 24, 62, 75, 16, 16],
[79, 51, 39, 71, 73, 52, 66],
[48, 22, 91, 92, 80, 95, 44]],
[[58, 0, 45, 18, 41, 81, 34],
[35, 0, 83, 12, 85, 43, 25],
[99, 33, 31, 85, 86, 4, 6]],
[[41, 72, 92, 44, 11, 30, 58],
[50, 56, 37, 70, 70, 21, 17],
[68, 55, 16, 71, 96, 45, 21]],
[[51, 97, 20, 32, 3, 93, 26],
[81, 10, 72, 32, 45, 71, 86],
[13, 61, 48, 96, 99, 73, 73]]], dtype=torch.int32)
"""
#抽取分数大于等于80分的分数(布尔索引),结果是1维张量
g = torch.masked_select(scores,scores>=80)
print(g)
# scores.ge(5) le表示<=5, ge表示>=5 gt >5 lt <5
"""tensor([80, 80, 85, 95, 90, 85, 91, 92, 80, 95, 81, 83, 85, 93, 99, 85, 86, 92,
90, 83, 82, 96, 97, 93, 82, 81, 86, 95, 96, 99], dtype=torch.int32)"""
#抽取第0个班级第0个学生的第0门课程,第2个班级的第3个学生的第1门课程,第3个班级的第4个学生第6门课程成绩
#take将输入看成一维数组,输出和index同形状
torch.take(scores,torch.tensor([0*5*7+0,2*5*7+3*7+1,3*5*7+4*7+6]))
"""tensor([48, 50, 73], dtype=torch.int32)"""
2.3 不规则索引切片更改
torch.where(condition, input, other, *, out=None) 可以理解为if的张量版本。
tensor.index_fill(dim, index, value)的选取元素逻辑和torch.index_select相同。
tensor.masked_fill(mask, value)的选取元素逻辑和torch.masked_select相同。
#如果分数大于60分,赋值成1,否则赋值成0
torch.where(scores>60,torch.tensor(1),torch.tensor(0))
"""
tensor([[[0, 0, 0, 1, 1, 0, 0],
[1, 0, 1, 1, 1, 0, 1],
[1, 0, 0, 1, 1, 0, 1],
[0, 0, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 0]],
[[0, 0, 0, 0, 0, 1, 0],
[0, 0, 1, 0, 0, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0, 0],
[1, 0, 0, 1, 1, 0, 0]],
[[0, 1, 1, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[1, 0, 0, 1, 1, 0, 0]],
[[0, 1, 0, 0, 0, 1, 0],
[0, 0, 1, 0, 0, 1, 0],
[1, 0, 1, 0, 0, 1, 1],
[1, 0, 1, 0, 0, 0, 0],
[0, 1, 0, 1, 1, 1, 1]]])
"""
#将每个班级第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],
[ 80, 45, 80, 85, 95, 43, 90],
[100, 100, 100, 100, 100, 100, 100],
[ 4, 15, 64, 85, 58, 36, 43],
[100, 100, 100, 100, 100, 100, 100]],
[[100, 100, 100, 100, 100, 100, 100],
[ 23, 44, 78, 54, 35, 18, 54],
[100, 100, 100, 100, 100, 100, 100],
[ 34, 28, 50, 13, 93, 12, 31],
[100, 100, 100, 100, 100, 100, 100]],
[[100, 100, 100, 100, 100, 100, 100],
[ 66, 90, 45, 3, 20, 48, 57],
[100, 100, 100, 100, 100, 100, 100],
[ 41, 50, 74, 83, 82, 21, 17],
[100, 100, 100, 100, 100, 100, 100]],
[[100, 100, 100, 100, 100, 100, 100],
[ 8, 37, 77, 58, 28, 82, 27],
[100, 100, 100, 100, 100, 100, 100],
[ 79, 43, 95, 34, 53, 29, 13],
[100, 100, 100, 100, 100, 100, 100]]], dtype=torch.int32)
"""
#将分数小于60分的分数赋值成60分
torch.masked_fill(scores,scores<60,60)
#等价于b = scores.masked_fill(scores<60,60)
"""
tensor([[[60, 60, 60, 62, 75, 60, 60],
[80, 60, 80, 85, 95, 60, 90],
[79, 60, 60, 71, 73, 60, 66],
[60, 60, 64, 85, 60, 60, 60],
[60, 60, 91, 92, 80, 95, 60]],
[[60, 60, 60, 60, 60, 81, 60],
[60, 60, 78, 60, 60, 60, 60],
[60, 60, 83, 60, 85, 60, 60],
[60, 60, 60, 60, 93, 60, 60],
[99, 60, 60, 85, 86, 60, 60]],
[[60, 72, 92, 60, 60, 60, 60],
[66, 90, 60, 60, 60, 60, 60],
[60, 60, 60, 70, 70, 60, 60],
[60, 60, 74, 83, 82, 60, 60],
[68, 60, 60, 71, 96, 60, 60]],
[[60, 97, 60, 60, 60, 93, 60],
[60, 60, 77, 60, 60, 82, 60],
[81, 60, 72, 60, 60, 71, 86],
[79, 60, 95, 60, 60, 60, 60],
[60, 61, 60, 96, 99, 73, 73]]], dtype=torch.int32)
"""
3. 维度变换
torch.reshape 可以改变张量的形状。(或者调用张量的view方法)
torch.transpose/torch.permute 可以交换维度。
torch.squeeze 可以减少维度。 torch.unsqueeze 可以增加维度。
3.1 reshape/view
如果只是想简单地重塑一个tensor的shape,那么就是用reshape,但是如果需要考虑内存的开销而且要确保重塑后的tensor与之前的tensor共享存储空间,那就使用view()。
view()方法只适用于满足连续性条件的tensor,并且该操作不会开辟新的内存空间,只是产生了对原存储空间的一个新别称和引用,返回值是视图。
而reshape()方法的返回值既可以是视图,也可以是副本,当满足连续性条件时返回view,否则返回副本[ 此时等价于先调用contiguous()方法在使用view() ]。因此当不确能否使用view时,可以使用reshape。reshape方法更强大,可以认为a.reshape = a.view() + a.contiguous().view()。
a = torch.arange(9.)
print(a)
a_reshape= torch.reshape(a, (-1, 3)) # -1自动推断
print(a_reshape)
#共内存
print(id(a.data)==id(a_reshape.data))
print(a_reshape.view(9))
b = a_reshape.permute(1, 0) # 转置
# print(b.view(9)) # 报错
3.2 transpose/permute
torch.transpose(input, dim0, dim1): 交换张量的两个维度,在图像的预处理中常用。
torch.
permute
(input, dims)
两者异同:
- permute相当于可以同时操作于tensor的若干维度,transpose只能同时作用于tensor的两个维度;
- torch.transpose(x)合法, x.transpose()合法。torch.permute(x)不合法,x.permute()合法。
- 与contiguous、view函数之关联。contiguous:view只能作用在contiguous的variable上,如果在view之前调用了transpose、permute等,就需要调用contiguous()来返回一个contiguous copy;一种可能的解释是:有些tensor并不是占用一整块内存,而是由不同的数据块组成,而tensor的view()操作依赖于内存是整块的,这时只需要执行contiguous()这个函数,把tensor变成在内存中连续分布的形式;判断ternsor是否为contiguous,可以调用torch.Tensor.is_contiguous()函数。
minval=0
maxval=255
# Batch,Height,Width,Channel
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])
”“”
3.3 squeeze/unsqueeze
torch.squeeze(input, dim=None, out=None): 压缩长度为1的维度,
torch. unsqueeze()增加一维
dim若为None,移除所有长度为1的轴,若指定维度,当且仅当该轴长度为1时可以被移除
# torch.squeeze
t = torch.rand((1, 2, 3, 1))
t_sq = torch.squeeze(t)
t_0 = torch.squeeze(t, dim=0)
t_1 = torch.squeeze(t, dim=1)
print(t_sq.shape) # torch.Size([2, 3])
print(t_0.shape) # torch.Size([2, 3, 1])
print(t_1.shape) # torch.Size([1, 2, 3, 1])
print(torch.unsqueeze(t_0,dim=0).shape) # torch.Size([1, 2, 3, 1])
4. 数学运算
torch.add(input, alpha=1, other, out=None): input+alpha * other
wx + b = torch.add(b,w,x)
#矩阵乘法
a = torch.tensor([[1,2],
[3,4]])
b = torch.tensor([[2,0],[0,2]])
print(a@b) #等价于torch.matmul(a,b) 或 torch.mm(a,b) a,b必须是tensor
#矩阵转置
a = torch.tensor([[1.0,2],[3,4]])
print(a.t())
乘法,a@b, torch.matmul(a,b) 或 torch.mm(a,b)
#矩阵乘法
a = torch.tensor([[1,2],
[3,4]])
b = torch.tensor([[2,0],[0,2]])
print(a@b) #等价于torch.matmul(a,b) 或 torch.mm(a,b) a,b必须是tensor
#矩阵转置
a = torch.tensor([[1.0,2],[3,4]])
print(a.t())
更多见github。
5. 搭建一个线性回归模型
关系线性:y=wx+b,任务就是求解w, b
步骤:
- 确定模型: Model :y = wx + b
- 选择损失函数: 这里用MSE :
- 求解梯度并更新w, b
w = w − LR w.grad
b= b−LR × w.grad
# 首先我们得有训练样本X,Y, 这里我们随机生成
x = torch.rand(20, 1) * 10
y = 2 * x + (5 + torch.randn(20, 1))
# 构建线性回归函数的参数
w = torch.randn((1), requires_grad=True)
b = torch.zeros((1), requires_grad=True) # 这俩都需要求梯度
for iteration in range(100):
# 前向传播
wx = torch.mul(w, x)
y_pred = torch.add(wx, b)
# 计算loss
loss = (0.5 * (y-y_pred)**2).mean()
# 反向传播
loss.backward()
# 更新参数
b.data.sub_(lr * b.grad) # 这种_的加法操作时从自身减,相当于-=
w.data.sub_(lr * w.grad)
# 梯度清零
w.grad.data.zero_()
b.grad.data.zero_()
print(w.data, b.data)
参考:
torch — PyTorch 1.10 documentation
torch.split() 与 torch.chunk()_torch.chunk(xr, m, dim=1)_Foneone的博客-CSDN博客
https://www.cnblogs.com/zjuhaohaoxuexi/p/15585775.html
PyTorch:view() 与 reshape() 区别详解_pytorch view reshape-CSDN博客
系统学习Pytorch笔记一:Pytorch的数据载体张量与线性回归_翻滚的小@强的博客-CSDN博客
GitHub - lyhue1991/eat_pytorch_in_20_days: Pytorch🍊🍉 is delicious, just eat it! 😋😋