本文是接着上一篇深度学习之 4 深度学习框架2_水w的博客-CSDN博客
目录
基本数据处理与计算操作
1、创建Tensor
在深度学习中,我们通常会频繁地对数据进行操作。而在PyTorch中, torch.Tensor 是存储和变换数据的主要工具。 Tensor 和 NumPy 的多维数组非常类似。 然而, Tensor 提供了GPU计算和自动求梯度等更多功能,这些使 Tensor 更加适合深度学习。"tensor"这个单词一般可译作“张量”,张量可以看作是一个多维数组。 标量 可以看作是0维张量, 向量 可以看作1维张量, 矩阵 可以看作是2维张量 ◼ 导入PyTorch
◼ 创建Tensor
import torch # 导入PyTorch
# 1、创建一个2x3的未初始化的Tensor
x = torch.empty(2, 3)
print(x)
# 输出结果:
Out [1]: tensor([[1.1710e+32, 4.5782e-41, 1.1710e+32], [4.5782e-41, 2.1459e+20, 9.2489e-04]])
# 2、创建一个2x3的随机初始化的Tensor
x = torch.rand(2, 3) # 随机分布,产生[0,1)区间里的数值
print(x)
# 输出结果:
Out [2]: tensor([[0.8891, 0.7304, 0.1292], [0.8943, 0.6942, 0.1651]])
# 3、创建一个2x3的long型全0的Tensor
x = torch.zeros(2, 3, dtype=torch.long) # dype指的是data数据的类型
print(x)
# 输出结果:
Out [3]: tensor([[0, 0, 0],[0, 0, 0]])
# 4、直接根据数据创建
x = torch.tensor([[5.5, 3],[2.2,5]])
print(x)
# 输出结果:
Out [4]: tensor([[5.5000, 3.0000], [2.2000, 5.0000]])
◼
我们还可以通过现有的
Tensor
来创建
此类方法会默认重用输入
Tensor
的一些属性,例如数据类型,除非自定义数据类型。
# 返回的tensor默认具有相同的torch.dtype和torch.device
x = x.new_ones(2, 3)
print(x)
# 指定新的数据类型
x = torch.randn_like(x, dtype=torch.float) # 生成的新张量与原来的张量的大小是相同的(2x3),只手将数据类型改为了浮点型
print(x)
Out [5]: tensor([[1., 1., 1.],[1., 1., 1.]], dtype=torch.float64)
tensor([[ 0.5344, 0.5095, 0.3691],[ 0.0160, 1.4369, 1.3419]])
◼ 通过shape或者size()来获取Tensor的形状
# 这两个函数调用之后都可以返回张量的形状
In [6]: # 返回的torch.Size其实就是一个tuple, 支持所有tuple的操作。
print(x.size())
print(x.shape)
Out [6]: torch.Size([2, 3])
torch.Size([2, 3])
◼ 其他创建Tensor的函数(可查阅官方API)
2、Tensor的相关操作
◼ 算术操作
在PyTorch中,同一种操作可能有很多种形式,下面用加法作为例子。
◼ 索引
我们还可以使用类似NumPy的索引操作来访问
Tensor
的一部分,
需要注意的是: 索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改。
其他更高级的选择函数(可查阅官方API):
◼ 改变形状 :用view()来改变Tensor的形状
y = x.view(6)
z = x.view(-1, 2) # 只给了一个维度是2,可以推算出前面-1指定的维度应该是3(-1所指的维度可以根据其他维度的值推出来)
print(x.size(), y.size(), z.size())
Out [3]: torch.Size([2, 3]) torch.Size([6]) torch.Size([3, 2])
注意: view() 返回的新 Tensor 与源 Tensor 虽然可能有不同的 size ,但是是共享 data 的,也即更改其中的一个,另外一个也会跟着改变。(顾名思义,view仅仅是改变了对这个张量的观察角度,内部数据并未改变)x += 1 print(x) print(y) # 发现y也加了1 Out [4]: tensor([[2.0910, 2.5265, 2.3833], [1.4564, 1.3117, 1.5181]]) tensor([2.0910, 2.5265, 2.3833, 1.4564, 1.3117, 1.5181])
◼ 如果我们想返回一个真正新的副本(即不共享data内存)该怎么办呢?Pytorch还提供了一个 reshape() 方法可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。我们推荐先用 clone() 创造一个副本然后再使用 view() 。x_cp = x.clone().view(6) x -= 1 print(x) print(x_cp) # 我们发现x没有变化,x_cp相对于x改变了 Out [5]: tensor([[1.0910, 1.5265, 1.3833], [0.4564, 0.3117, 0.5181]]) tensor([2.0910, 2.5265, 2.3833, 1.4564, 1.3117, 1.5181])
另外,PyTorch还支持一些线性函数,具体用法可参考官方文档。
3、广播机制
x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)
# 由于 x 和 y 分别是1行2列和3行1列的矩阵,要计算x+y,那么x中第一行的2个元素被广播(复制) 到了第二行和第三行,而 y 中第一列的3个元素被广播(复制)到了第二列。如此,就可以对2个3行2列的矩阵按元素相加。
Out [1]: tensor([[1, 2]])
tensor([[1],
[2],
[3]])
tensor([[2, 3],
[3, 4],
[4, 5]])
由于 x 和 y 分别是1行2列和3行1列的矩阵,如果要计算 x + y,那么 x 中第一行的2个元素被广播(复制) 到了第二行和第三行,而 y 中第一列的3个元素被广播(复制)到了第二列。如此,就可以对2个3行2列的矩阵按元素相加。
当我们对两个形状不同的Tensor按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个Tensor形状相同后再按元素运算。
4、Tensor和NumPy相互转换
- numpy()和from_numpy()
- torch.tensor()
◼
我们可以使用
numpy()和from_numpy()
将Tensor和NumPy中的数组相互转换。但是需要注意的一点是: 这两个函数所产生的
Tensor
和
NumPy
中的数组共享相同的内存(所以他们之间的转换很快),改变其
中一个时另一个也会改变!
◼ 还有一个常用的将NumPy中的array转换成Tensor的方法就是torch.tensor(), 需要注意的是,此方法总是会进行数据拷贝(就会消耗更多的时间和空间),所以返回的Tensor和原来的数据不再共享内存。
5、Tensor on GPU
用方法 to() 可以将Tensor在CPU和GPU(需要硬件支持)之间相互移动。
6、自动求梯度
在深度学习中,我们经常需要对函数求梯度(gradient)。PyTorch提供的 autograd 包能够根据输入和前向传播过程自动构建计算图,并执行反向传播。Tensor 是 autograd 包的核心类, 如果将其属性 .requires_grad 设置为 True ,它将开始追踪(track)在其上的所有操作(这样就可以利用链式法则进行梯度传播了)。完成计算后,可以调用 .backward() 来完成所有梯度计算。此 Tensor 的梯度将累积到 .grad 属性中。如果不想要被继续追踪,可以调用 .detach() 将其从追踪记录中分离出来,这样就可以防止将来的计算被追踪,这样梯度就传不过去了。此外,还可以用 with torch.no_grad() 将不想被追踪的操作代码块包裹起来,这种方法在评估模型的时候很常用 , 因为在评估模型时 , 我们并不需要计算可训练参数( requires_grad=True )的梯度。Function 是另外一个很重要的类。 Tensor 和 Function 互相结合就可以构建一个记录有整个计算过程的有向无环图(DAG)。每个 Tensor 都有一个 .grad_fn 属性,该属性即创建该 Tensor 的 Function , 就是说该 Tensor 是不是通过某些运算得到的,若是,则 grad_fn 返回一个与这些运算相关的对象,否则是None。
例如:
梯度
◼
x = torch.ones(2, 2, requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
out.backward() # 等价于 out.backward(torch.tensor(1.))
print(x.grad)
Out [1]: tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前都需要把梯度清零。
◼
注意:在y.backward()时,
- 如果y是标量,则不需要为backward()传入任何参数;
- 否则,需要传入一个与y同形的Tensor。这样的原因简单来说就是为了避免向量(甚至更高维张量)对张量求导,因此转换成标量对张量进行求导。
举例:
假设形状为
𝒎 × 𝒏
的矩阵
𝐗
经过运算得到了
𝒑 × 𝒒
的矩阵
𝐘
,
𝐘
又经过运算得到了
𝒔 × 𝒕
的矩阵
𝐙
。那么按照梯度计算的规则,
𝒅𝒁/𝒅𝒀
应该是一个
𝒔 × 𝒕 × 𝒑 × 𝒒
四维张量,
𝒅𝒀/𝒅𝑿
是一个
𝒑 × 𝒒 × 𝒎 × 𝒏
的四维张量,而如何对4维张量进行计算是一个比较复杂的问题。 为了避免这个问题,我们不允许张量对张量求导,只允许标量对张量求导,求导结果是和自变量同形的张量
。
所以必要时我们要
把张量通过将所有张量的元素加权求和的方式转换为标量
,举个例子,假设
𝒚
由自变量
𝒙
计算而来,
𝒘
是和
𝒚
同形的张量,则
𝐲. 𝐛𝐚𝐜𝐤𝐰𝐚𝐫𝐝(𝒘)
的含义是:先计算
𝒍 = 𝐭𝐨𝐫𝐜𝐡. 𝐬𝐮𝐦(𝒚 ∗ 𝒘)
,因此
𝒍
是一个标量,然后我们再求
𝒍
对自变量
𝒙
的导数。