PyTorch浅尝辄止(2)

Tensor谜题

力学中的张量

在数学上,一个张量是一个多维数组。在流体力学中,张量与场相关。场描述三维空间中的标量或者适量的分布。在三维空间中,一个标量场通常用等值面来表示。

在空间区域 Ω \Omega Ω中的函数或者数量场 u = u ( x , y , z ) u = u(x,y,z) u=u(x,y,z) Ω \Omega Ω中有连续的一阶偏导数,则曲面 u ( x , y , z ) = C u(x,y,z)=C u(x,y,z)=C称为等值面。常见的如等温面、等压面。等值面的特点是:

  1. 等值面彼此不相交;
  2. 等值面的疏密表达标量函数的变化情况。

标量场的梯度可以表达为适量的形式:

d u ( x , y , z ) = [ ∂ u ∂ x ∂ u ∂ y ∂ u ∂ z ] \mathbf{du}(x,y,z) = \left[\begin{array}{c} \frac{\partial u}{\partial x} \\ \frac{\partial u}{\partial y} \\ \frac{\partial u}{\partial z} \end{array}\right] du(x,y,z)=xuyuzu

这就构成一个矢量场。如果函数 u = u ( x , y , z ) u=u(x,y,z) u=u(x,y,z)是二阶连续的,那么上述矢量场的梯度可以表达为2阶张量的形式:

d 2 u ( x , y , z ) = [ ∂ 2 u ∂ x 2 ∂ 2 u ∂ x ∂ y ∂ 2 u ∂ x ∂ z ∂ 2 u ∂ x ∂ y ∂ 2 u ∂ y 2 ∂ 2 u ∂ y ∂ z ∂ 2 u ∂ x ∂ z ∂ 2 u ∂ y ∂ z ∂ 2 u ∂ z 2 ] \mathbf{d^2u}(x,y,z) = \left[\begin{array}{ccc} \frac{\partial^2 u}{\partial x^2} & \frac{\partial^2 u}{\partial x \partial y} & \frac{\partial^2 u}{\partial x \partial z}\\ \frac{\partial^2 u}{\partial x \partial y} & \frac{\partial^2 u}{\partial y^2} & \frac{\partial^2 u}{\partial y \partial z} \\ \frac{\partial^2 u}{\partial x \partial z} & \frac{\partial^2 u}{\partial y \partial z} & \frac{\partial^2 u}{\partial z^2} \end{array}\right] d2u(x,y,z)=x22uxy2uxz2uxy2uy22uyz2uxz2uyz2uz22u

当然还可以继续下去,每次求导都增加一个维度,从标量场到矢量场,到2阶张量场,到3阶张量场,这就是张量给人的感觉……

说了这一堆,我也不知道为什么要这么复杂。我只是想说张量是一个多维数组而已。

机器学习中的张量

在机器学习中,张量通常用各种形状的矩阵表示,矩阵的维数就是张量的阶数。

在深度学习中,张量通常用于表示神经网络的权重和激活。

在PyTorch中,张量是一个类似于NumPy数组的对象,它可以在GPU上使用以加速计算。张量与NumPy数组之间的主要区别是张量可以在GPU上使用以加速计算。

简单的张量

上面是瞎扯的部分。以下就开始搬砖。一件事情只要落到搬砖或者焊钢板上,我就会很开心。我就是一个搬砖的。

搬砖,哦不,张量的创建

在PyTorch中,张量可以通过以下方式创建:

import numpy as np
import torch
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)


def show_tensor(x_t):
    print(f"Tensor的数据类型: {x_t.dtype}")
    print(f"Tensor的设备: {x_t.device}")
    print(f"Tensor的维度: {x_t.dim()}")
    print(f"Tensor的形状: {x_t.shape}")
    print(f"Tensor的元素个数: {x_t.numel()}")
    print(f"Tensor的字符串表示:\n{x_t}")

    print("Tensor的遍历:")
    for i, xi in enumerate(x_t):
        print(f"\t{i}:{xi}")
    print(f"Tensor的索引: x_t[0, 0]={x_t[0, 0]}")


show_tensor(x_data)
Tensor的数据类型: torch.int64
Tensor的设备: cpu
Tensor的维度: 2
Tensor的形状: torch.Size([2, 2])
Tensor的元素个数: 4
Tensor的字符串表示:
tensor([[1, 2],
        [3, 4]])
Tensor的遍历:
	0:tensor([1, 2])
	1:tensor([3, 4])
Tensor的索引: x_t[0, 0]=1

还可以从NumPy数组创建张量,以及从其它张量来创建张量。

print("-------------------------\n从NumPy数组创建张量")
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
show_tensor(x_np)

print("\n-------------------------\n从其它张量来创建张量")
x_ones = torch.ones_like(x_data)
show_tensor(x_ones)

print("\n-------------------------\n随机初始化张量")
x_rand = torch.rand_like(x_data, dtype=torch.float)
show_tensor(x_rand)
-------------------------
从NumPy数组创建张量
Tensor的数据类型: torch.int32
Tensor的维度: 2
Tensor的形状: torch.Size([2, 2])
Tensor的元素个数: 4
Tensor的字符串表示:
tensor([[1, 2],
        [3, 4]], dtype=torch.int32)
Tensor的遍历:
	0:tensor([1, 2], dtype=torch.int32)
	1:tensor([3, 4], dtype=torch.int32)
Tensor的索引: x_t[0, 0]=1

-------------------------
从其它张量来创建张量
Tensor的数据类型: torch.int64
Tensor的维度: 2
Tensor的形状: torch.Size([2, 2])
Tensor的元素个数: 4
Tensor的字符串表示:
tensor([[1, 1],
        [1, 1]])
Tensor的遍历:
	0:tensor([1, 1])
	1:tensor([1, 1])
Tensor的索引: x_t[0, 0]=1

-------------------------
随机初始化张量
Tensor的数据类型: torch.float32
Tensor的维度: 2
Tensor的形状: torch.Size([2, 2])
Tensor的元素个数: 4
Tensor的字符串表示:
tensor([[0.0646, 0.8117],
        [0.3862, 0.2779]])
Tensor的遍历:
	0:tensor([0.0646, 0.8117])
	1:tensor([0.3862, 0.2779])
Tensor的索引: x_t[0, 0]=0.06455862522125244

更多的创建函数可以参考官方文档。例如可以从其形状来创建张量,并用各种方式来填充。

shape = (2, 3,)

print("\n-------------------------\n从均匀分布的随机变量填充张量")
x_rand = torch.rand(shape)
show_tensor(x_rand)

print("\n-------------------------\n用高斯分布的随机量填充")
x_randn = torch.randn(shape)
show_tensor(x_randn)

print("\n-------------------------\n用0填充")
x_zeros = torch.zeros(shape)
show_tensor(x_zeros)

print("\n-------------------------\n用1填充")
x_ones = torch.ones(shape)
show_tensor(x_ones)

print("\n-------------------------\n用指定值填充")
x_fill = torch.full(shape, 3.14)
show_tensor(x_fill)


-------------------------
从均匀分布的随机变量填充张量
Tensor的数据类型: torch.float32
Tensor的设备: cpu
Tensor的维度: 2
Tensor的形状: torch.Size([2, 3])
Tensor的元素个数: 6
Tensor的字符串表示:
tensor([[0.8780, 0.6692, 0.2388],
        [0.3589, 0.1243, 0.3470]])
Tensor的遍历:
	0:tensor([0.8780, 0.6692, 0.2388])
	1:tensor([0.3589, 0.1243, 0.3470])
Tensor的索引: x_t[0, 0]=0.8779925107955933

-------------------------
用高斯分布的随机量填充
Tensor的数据类型: torch.float32
Tensor的设备: cpu
Tensor的维度: 2
Tensor的形状: torch.Size([2, 3])
Tensor的元素个数: 6
Tensor的字符串表示:
tensor([[-0.1044,  0.6825,  0.3588],
        [-0.7004,  1.5626,  0.8914]])
Tensor的遍历:
	0:tensor([-0.1044,  0.6825,  0.3588])
	1:tensor([-0.7004,  1.5626,  0.8914])
Tensor的索引: x_t[0, 0]=-0.1043904721736908

-------------------------
用0填充
Tensor的数据类型: torch.float32
Tensor的设备: cpu
Tensor的维度: 2
Tensor的形状: torch.Size([2, 3])
Tensor的元素个数: 6
Tensor的字符串表示:
tensor([[0., 0., 0.],
        [0., 0., 0.]])
Tensor的遍历:
	0:tensor([0., 0., 0.])
	1:tensor([0., 0., 0.])
Tensor的索引: x_t[0, 0]=0.0

-------------------------
用1填充
Tensor的数据类型: torch.float32
Tensor的设备: cpu
Tensor的维度: 2
Tensor的形状: torch.Size([2, 3])
Tensor的元素个数: 6
Tensor的字符串表示:
tensor([[1., 1., 1.],
        [1., 1., 1.]])
Tensor的遍历:
	0:tensor([1., 1., 1.])
	1:tensor([1., 1., 1.])
Tensor的索引: x_t[0, 0]=1.0

-------------------------
用指定值填充
Tensor的数据类型: torch.float32
Tensor的设备: cpu
Tensor的维度: 2
Tensor的形状: torch.Size([2, 3])
Tensor的元素个数: 6
Tensor的字符串表示:
tensor([[3.1400, 3.1400, 3.1400],
        [3.1400, 3.1400, 3.1400]])
Tensor的遍历:
	0:tensor([3.1400, 3.1400, 3.1400])
	1:tensor([3.1400, 3.1400, 3.1400])
Tensor的索引: x_t[0, 0]=3.140000104904175

张量支持的运算

张量支持的运算有很多,这里只列举一些常用的。官方文档中有超过100个运算函数,可以参考官方文档。所有这些操作都可以在GPU上执行,只需要将张量移动到GPU上即可。默认情况下,张量在CPU上创建。需要调用to方法将张量移动到GPU上。

# 如果有GPU,就将张量移动到GPU上
if torch.cuda.is_available():
    device = torch.device("cuda")
    x_data = x_data.to(device)
    x_np = x_np.to(device)
    x_ones = x_ones.to(device)
    x_rand = x_rand.to(device)
    print("张量已经移动到GPU上")
else:
    device = torch.device("cpu")
    print("没有GPU,张量仍然在CPU上")
没有GPU,张量仍然在CPU上

张量的运算和操作大概可以分为以下几类:

  • 形状操作
  • 索引操作
  • 数学运算
  • 线性代数运算
  • 随机数
  • 序列操作
  • 并行操作
  • 其它操作
shape = (2, 2,)
x_data = torch.rand(shape)

print("张量的形状操作")
print(f"张量的形状: {x_data.shape}")
print(f"张量的元素个数: {x_data.numel()}")
print(f"张量的维度: {x_data.dim()}")
print(f"随机张量: \n{x_data}")
print(f"张量的转置: \n{x_data.t()}")
print(f"张量的展平(-1,):\n{x_data.view(-1)}")
print(f"张量的展平(4,):\n{x_data.view(4)}")
print(f"张量的展平(2,2):\n{x_data.view(2, 2)}")
print(f"张量的展平(1,4):\n{x_data.view(1, 4)}")
print(f"张量的展平(1,-1):\n{x_data.view(1, -1)}")
print(f"张量的展平(-1,1):\n{x_data.view(-1, 1)}")
print(f"张量的展平(2,-1):\n{x_data.view(2, -1)}")
print(f"张量的展平(-1,2):\n{x_data.view(-1, 2)}")
张量的形状操作
张量的形状: torch.Size([2, 2])
张量的元素个数: 4
张量的维度: 2
随机张量: 
tensor([[0.0734, 0.9233],
        [0.8115, 0.4401]])
张量的转置: 
tensor([[0.0734, 0.8115],
        [0.9233, 0.4401]])
张量的展平(-1,):
tensor([0.0734, 0.9233, 0.8115, 0.4401])
张量的展平(4,):
tensor([0.0734, 0.9233, 0.8115, 0.4401])
张量的展平(2,2):
tensor([[0.0734, 0.9233],
        [0.8115, 0.4401]])
张量的展平(1,4):
tensor([[0.0734, 0.9233, 0.8115, 0.4401]])
张量的展平(1,-1):
tensor([[0.0734, 0.9233, 0.8115, 0.4401]])
张量的展平(-1,1):
tensor([[0.0734],
        [0.9233],
        [0.8115],
        [0.4401]])
张量的展平(2,-1):
tensor([[0.0734, 0.9233],
        [0.8115, 0.4401]])
张量的展平(-1,2):
tensor([[0.0734, 0.9233],
        [0.8115, 0.4401]])
shape = (2, 2,)
x_data = torch.rand(shape)

print("张量的索引操作")
print(f"张量的第0行: {x_data[0]}")
print(f"张量的第0行第0列: {x_data[0, 0]}")
print(f"张量的第0行第1列: {x_data[0, 1]}")
print(f"张量的第1行第0列: {x_data[1, 0]}")
print(f"张量的第1行第1列: {x_data[1, 1]}")
print(f"张量的第0行第0列: {x_data[0][0]}")
print(f"张量的第0行第1列: {x_data[0][1]}")
print(f"张量的第1行第0列: {x_data[1][0]}")
print(f"张量的第1行第1列: {x_data[1][1]}")
张量的索引操作
张量的第0行: tensor([0.0734, 0.9233])
张量的第0行第0列: 0.0734146237373352
张量的第0行第1列: 0.9233333468437195
张量的第1行第0列: 0.8114538192749023
张量的第1行第1列: 0.440138041973114
张量的第0行第0列: 0.0734146237373352
张量的第0行第1列: 0.9233333468437195
张量的第1行第0列: 0.8114538192749023
张量的第1行第1列: 0.440138041973114
shape = (2, 2,)
x_data = torch.rand(shape)

print("张量的数学运算")
print(f"张量的加法: \n{x_data + x_data}")
print(f"张量的减法: \n{x_data - x_data}")
print(f"张量的乘法: \n{x_data * x_data}")
print(f"张量的除法: \n{x_data / x_data}")
print(f"张量的幂运算: \n{x_data ** 2}")
print(f"张量的平方根: \n{x_data ** 0.5}")
print(f"张量的指数运算: \n{x_data.exp()}")
print(f"张量的对数运算: \n{x_data.log10()}")
print(f"张量的三角函数运算: \n{x_data.sin()}")
print(f"张量的比较运算: \n{x_data > 0.5}")
print(f"张量的求和: \n{x_data.sum()}")
print(f"张量的求均值: \n{x_data.mean()}")
print(f"张量的求最大值: \n{x_data.max()}")
print(f"张量的求最小值: \n{x_data.min()}")
print(f"张量的求标准差: \n{x_data.std()}")
print(f"张量的求方差: \n{x_data.var()}")

张量的数学运算
张量的加法: 
tensor([[0.1468, 1.8467],
        [1.6229, 0.8803]])
张量的减法: 
tensor([[0., 0.],
        [0., 0.]])
张量的乘法: 
tensor([[0.0054, 0.8525],
        [0.6585, 0.1937]])
张量的除法: 
tensor([[1., 1.],
        [1., 1.]])
张量的幂运算: 
tensor([[0.0054, 0.8525],
        [0.6585, 0.1937]])
张量的平方根: 
tensor([[0.2710, 0.9609],
        [0.9008, 0.6634]])
张量的指数运算: 
tensor([[1.0762, 2.5177],
        [2.2512, 1.5529]])
张量的对数运算: 
tensor([[-1.1342, -0.0346],
        [-0.0907, -0.3564]])
张量的三角函数运算: 
tensor([[0.0733, 0.7976],
        [0.7253, 0.4261]])
张量的比较运算: 
tensor([[False,  True],
        [ True, False]])
张量的求和: 
2.248339891433716
张量的求均值: 
0.562084972858429
张量的求最大值: 
0.9233333468437195
张量的求最小值: 
0.0734146237373352
张量的求标准差: 
0.38572657108306885
张量的求方差: 
0.14878499507904053
shape = (2, 2,)
x_data = torch.rand(shape)

print("张量的线性代数运算")
print(f"张量的转置: \n{x_data.t()}")
print(f"张量的矩阵乘法: \n{x_data.mm(x_data.t())}")
print(f"张量的矩阵乘法: \n{x_data @ x_data.t()}")
print(f"张量的矩阵乘法: \n{x_data.matmul(x_data.t())}")
print(f"张量的矩阵乘法: \n{torch.mm(x_data, x_data.t())}")
print(f"张量的矩阵乘法: \n{torch.matmul(x_data, x_data.t())}")

x_data = torch.tensor([1, 2, 3, 4], dtype=torch.float32)
print(f"张量的点乘: \n{torch.dot(x_data, x_data.t())}")
print(f"张量的内积: \n{torch.inner(x_data, x_data.t())}")
print(f"张量的外积: \n{torch.outer(x_data, x_data.t())}")
print(f"张量的kron: \n{torch.kron(x_data, x_data.t())}")

张量的线性代数运算
张量的转置: 
tensor([[0.2352, 0.4036],
        [0.5942, 0.5712]])
张量的矩阵乘法: 
tensor([[0.4084, 0.4343],
        [0.4343, 0.4892]])
张量的矩阵乘法: 
tensor([[0.4084, 0.4343],
        [0.4343, 0.4892]])
张量的矩阵乘法: 
tensor([[0.4084, 0.4343],
        [0.4343, 0.4892]])
张量的矩阵乘法: 
tensor([[0.4084, 0.4343],
        [0.4343, 0.4892]])
张量的矩阵乘法: 
tensor([[0.4084, 0.4343],
        [0.4343, 0.4892]])
张量的点乘: 
30.0
张量的内积: 
30.0
张量的外积: 
tensor([[ 1.,  2.,  3.,  4.],
        [ 2.,  4.,  6.,  8.],
        [ 3.,  6.,  9., 12.],
        [ 4.,  8., 12., 16.]])
张量的kron: 
tensor([ 1.,  2.,  3.,  4.,  2.,  4.,  6.,  8.,  3.,  6.,  9., 12.,  4.,  8.,
        12., 16.])
shape = (2, 2,)

print("张量的随机生成")
print(f"张量的均匀分布随机数: \n{torch.rand(shape)}")
print(f"张量的标准正态分布随机数: \n{torch.randn(shape)}")
print(f"张量的正态分布随机数: \n{torch.normal(mean=torch.full(shape, 0.5), std=torch.full(shape, 0.1))}")
print(f"张量的随机整数: \n{torch.randint(low=0, high=10, size=shape)}")


张量的随机生成
张量的均匀分布随机数: 
tensor([[0.7879, 0.6105],
        [0.1430, 0.3645]])
张量的标准正态分布随机数: 
tensor([[ 0.3791,  0.7605],
        [-0.2999,  1.1909]])
张量的正态分布随机数: 
tensor([[0.4994, 0.4389],
        [0.6507, 0.7285]])
张量的随机整数: 
tensor([[1, 0],
        [0, 8]])

值得注意的一点是,张量可以通过计算得到一个新的张量,也有本地计算模式,也就是把运算的结果直接存储在原来的张量中,这样可以节省内存空间,但是需要注意的是,这种模式下,如果原来的张量被其他变量引用,那么这个变量的值也会发生改变,这是因为这两个变量指向的是同一个内存地址,所以会发生改变,这种情况下,我们可以使用clone()方法来复制一个张量,这样就不会影响到原来的张量了。一般的规则就是在运算函数后面增加一个下划线,就是本地计算模式,比如add_()t_()等。

shape = (2, 2,)
x_data = torch.randint(low=0, high=10, size=shape)
y_data = torch.randint(low=0, high=10, size=shape)

print("张量的本地计算模式")
print(f"张量x_data: \n{x_data}")
print(f"张量y_data: \n{y_data}")
print(f"张量的加法add: \n{x_data.add(y_data)}")
print(f"张量x_data: \n{x_data}")
print(f"张量的加法add_: \n{x_data.add_(y_data)}")
print(f"张量x_data: \n{x_data}")
张量的本地计算模式
张量x_data: 
tensor([[7, 7],
        [3, 3]])
张量y_data: 
tensor([[6, 8],
        [5, 0]])
张量的加法add: 
tensor([[13, 15],
        [ 8,  3]])
张量x_data: 
tensor([[7, 7],
        [3, 3]])
张量的加法add_: 
tensor([[13, 15],
        [ 8,  3]])
张量x_data: 
tensor([[13, 15],
        [ 8,  3]])

张量与Numpy数组的转换

张量和Numpy数组之间可以相互转换,这样就可以方便的使用Numpy的各种函数了,但是需要注意的是,张量和Numpy数组共享内存,也就是说,如果张量和Numpy数组中的一个发生改变,另一个也会发生改变,这是因为这两个变量指向的是同一个内存地址,所以会发生改变,这种情况下,我们可以使用clone()方法来复制一个张量,这样就不会影响到原来的张量了。

这一点在后面开始自动微分的时候就会变得很关键,所以需要注意一下。

shape = (2, 2,)
x_data = torch.randint(low=0, high=10, size=shape)
x_np = x_data.numpy()
print(f"张量x_data: \n{x_data}")
x_np[0, 0] = 100
print(f"张量x_data: \n{x_data}")

x_np = np.random.rand(2, 2)
x_data = torch.from_numpy(x_np)
print(f"x_np: \n{x_np}")
x_data[0, 0] = 100
print(f"x_np: \n{x_np}")
张量x_data: 
tensor([[1, 5],
        [6, 7]])
张量x_data: 
tensor([[100,   5],
        [  6,   7]])
x_np: 
[[0.92873644 0.20536021]
 [0.27923725 0.74282402]]
x_np: 
[[100.           0.20536021]
 [  0.27923725   0.74282402]]

总结

  1. 张量是PyTorch中最基本的数据结构,可以看作是一个多维数组,可以通过torch.tensor()方法来创建一个张量,也可以通过torch.rand()torch.randn()torch.normal()torch.randint()等方法来创建一个张量。
  2. 张量可以通过计算得到一个新的张量,也有本地计算模式,也就是把运算的结果直接存储在原来的张量中。
  3. 张量和Numpy数组之间可以相互转换,这样就可以方便的使用Numpy的各种函数了,但是需要注意的是,张量和Numpy数组共享内存,也就是说,如果张量和Numpy数组中的一个发生改变,另一个也会发生改变,这是因为这两个变量指向的是同一个内存地址。
posted @ 2023-04-15 11:15  大福是小强  阅读(10)  评论(0编辑  收藏  举报  来源