Tensor
"""
Tensor又名张量,读者可能对这个名词似曾相识,因它不仅在PyTorch中出现过,它也是Theano、TensorFlow、 Torch和MxNet中重要的数据结构。
关于张量的本质不乏深度的剖析,但从工程角度来讲,可简单地认为它就是一个数组,且支持高效的科学计算。
它可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)和更高维的数组(高阶数据)。
Tensor和Numpy的ndarrays类似,但PyTorch的tensor支持GPU加速。
"""
from __future__ import print_function
import torch as t
#torch的版本
print(t.__version__)
"""
3.1.1基础操作
从接口的角度来讲,对tensor的操作可分为两类:
1.torch.function,如torch.save等。
2.另一类是tensor.function,如tensor.view等。
为方便使用,对tensor的大部分操作同时支持这两类接口,在本书中不做具体区分,如torch.sum (torch.sum(a, b))与tensor.sum (a.sum(b))功能等价。
而从存储的角度来讲,对tensor的操作又可分为两类:
不会修改自身的数据,如 a.add(b), 加法的结果会返回一个新的tensor。
会修改自身的数据,如 a.add_(b), 加法的结果仍存储在a中,a被修改了。
函数名以_结尾的都是inplace方式, 即会修改调用者自己的数据,在实际应用中需加以区分
"""
"创建Tensor"
a=t.Tensor(2,3)#指定tensor的形状
print(a)#数值取决于内存空间的状态,print时候可能overflow
b=t.Tensor([[1,2,3],[4,5,6]])#用list的数据创建tensor
print(b)
print(b.tolist())#把tensor转为list
b_size=b.size()#tensor.size()返回torch.Size对象
print(b_size)
print(b.numel())#b中的元素总个数,2*3,等价于b.nelement()
c=t.Tensor(b_size)#创建一个与b形状一样的tensor
d=t.Tensor((2,3))#创建一个元素W为2和3的tensor
print(c,d)
#除了tensor.size(),还可以利用tensor.shape直接查看tensor的形状,tensor.shape等价于tensor.size()
print(c.shape)
"""
需要注意的是,t.Tensor(*sizes)创建tensor时,系统不会马上分配空间,
只是会计算剩余的内存是否足够使用,使用到tensor时才会分配,而其它操作都是在创建完tensor之后马上进行空间分配。
"""
"其他常用的创建tensor的方法"
print(t.ones(2,3))
print(t.zeros(2,3))
print(t.arange(1,6,2).shape)
print(t.linspace(1,10,3))
print(t.randn(2,3,device=t.device('cpu')))
print(t.randperm(5))#长度为5的随机排列
print(t.eye(2,4,dtype=t.int))#对角线为1,不要求行列数一致
scalar=t.tensor(3.14159)
print('scalar: %s, shape of sclar: %s' %(scalar, scalar.shape))
vector=t.tensor([1,2])
print('vector: %s, shape of vector: %s' %(vector, vector.shape))
tensor = t.Tensor(1,2) # 注意和t.tensor([1, 2])的区别
print(tensor.shape)
matrix = t.tensor([[0.1, 1.2], [2.2, 3.1], [4.9, 5.2]])
print(matrix,matrix.shape)
empty_tensor = t.tensor([])
print(empty_tensor.shape)
"""
常用Tensor操作
通过tensor.view方法可以调整tensor的形状,但必须保证调整前后元素总数一致。
view不会修改自身的数据,返回的新tensor与源tensor共享内存,也即更改其中的一个,另外一个也会跟着改变。
在实际应用中可能经常需要添加或减少某一维度,这时候squeeze和unsqueeze两个函数就派上用场了。
"""
a=t.arange(0,6)
print(a.view(2,3))
b=a.view(-1,3)#当某一维为-1的时候,会自动计算它的大小
print(b.size())
b.unsqueeze(1)#注意,在第一维上增加'1'(下标从0开始)
print(b[:,None].shape)
b.unsqueeze(-2)#-2表示倒数第二个维度
print(b[None,:].shape)
c=b.view(1,1,1,2,3)
print(c.squeeze(0))#压缩第0维的'1'
print(c.squeeze())#把所有维度为‘1’的压缩
a[1]=100
print(b)# a修改,b作为view之后的,也会跟着修改
"""
resize是另一种可用来调整size的方法,但与view不同,它可以修改tensor的大小。
如果新大小超过了原大小,会自动分配新的内存空间,而如果新大小小于原大小,则之前的数据依旧会被保存,看一个例子
"""
print(b.resize_(1,3))
print(b.resize_(3,3))#旧的数据依旧保存,多出的大小会分配空间
"""
索引操作
"""
a=t.randn(3,4)
print(a)
print(a[0])#第0行(下标从0开始)
print(a[:,0])#第0列
print(a[0][2])# 第0行第2个元素,等价于a[0, 2]
print(a[:2])#前两行
print(a[:2,0:2])# 前两行,第0,1列
print(a[0:1, :2]) # 第0行,前两列
print(a[0, :2]) # 注意两者的区别:形状不同
# None类似于np.newaxis, 为a新增了一个轴
# 等价于a.view(1, a.shape[0], a.shape[1])
print(a[None].shape)
print(a[:,None,:].shape)
print(a[:,None,None,:,None].shape)
"""
高级索引
"""
x=t.arange(0,27).view(3,3,3)
print(x)
print(x[[1,2],[1,2],[2,0]])# x[1,1,2]和x[2,2,0]
print(x[[2,1,0],[0],[1]])# x[2,0,1],x[1,0,1],x[0,0,1]
print(x[[0, 2], ...]) # x[0] 和 x[2]
"""
逐元素操作
这部分操作会对tensor的每一个元素(point-wise,又名element-wise)进行操作,
此类操作的输入与输出形状一致
"""
a = t.arange(0,6).view(2,3)
print("a:",a)
#print("t.cos(a):",t.cos(a))
print("a % 3:",a % 3) # t.fmod(a, 3)
print("a ** 2:",a ** 2) # t.pow(a, 2)
print("t.clamp(a, min=2, max=4)",t.clamp(a,min=2,max=4))
# 取a中的每一个元素与3相比较大的一个 (小于3的截断成3)
print(a)
print(t.clamp(a, min=3))
#b=a.sin_()# 效果同 a = a.sin();b=a ,但是更高效节省显存
#print(b)
"""
归并操作
此类操作会使输出形状小于输入形状,并可以沿着某一维度进行指定操作。
如加法sum,既可以计算整个tensor的和,也可以计算tensor中每一行或每一列的和
以上大多数函数都有一个参数dim,用来指定这些操作是在哪个维度上执行的。关于dim(对应于Numpy中的axis)的解释众说纷纭,这里提供一个简单的记忆方式:
假设输入的形状是(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。注意,以上只是经验总结,并非所有函数都符合这种形状变化方式,如cumsum。
"""
b = t.ones(2,3)
print("b.sum():",b.sum(dim=0,keepdim=True))
print("b.sum():",b.sum(dim=0,keepdim=False))# keepdim=False,不保留维度"1",注意形状
a = t.arange(0, 6).vie