pytorch系列2——张量结构操作

本文以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(inputdims

两者异同:

  • 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

步骤:

  1. 确定模型: Model :y = wx + b
  2. 选择损失函数: 这里用MSE :\frac{1}{m}\sum (y_i - \widehat{y})^2
  3. 求解梯度并更新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! 😋😋

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值