Tensor
torch中的Tensor是一种数据结构,使用上与Python的list、numpy的array、ndarray等数据结构类似,可以当成一个多维数组来用。 数学上对张量有特定定义,但通常理解为多维数组即可。
生成Tensor:torch包中提供了直接生成Tensor的函数,如 zeros()、ones()、rand() 等。 还可以用 tensor(data) 函数直接将表示数组的数据(如:list、numpy.ndarray等格式)转换为Tensor。 可通过 from_numpy(data) 函数将numpy.ndarray格式的数据转换为Tensor。 也可生成一个与其他Tensor具有相同dtype和device等属性的Tensor, 使用torch的 ones_like(data) 或 rand_like(data) 等函数,或Tensor的 new_ones() 等函数。
Tensor的属性: shape(返回torch.Size格式)(也可以用size()函数),dtypedevice。
Tensor的操作:
类似numpy的API;改变原数据的原地操作在函数后面加_就可以(一般不建议这么操作)
• 索引
• 切片 • join:cat(tensors)或stack(tensors)
• 加法:add()或+ • 乘法:对元素层面的乘法mul()或*,矩阵乘法matmul()或@
• resize :
(1)reshape()或view(),建议使用reshape(),仅使用view()可能会造成Tensor不contiguous的问题
(2)squeeze()去掉长度为1的维度
(3)unsqueeze()增加一个维度(长度为1)
(4)transpose()转置2个维度
5. Tensor.numpy() 可将Tensor转换为numpy数据。 注意这两方向的转换的数据对象都是占用同一储存空间,修改后变化也会体现在另一对象上。
6. item()函数返回仅有一个元素的Tensor的该元素值。
Autograd
torch.autograd 是PyTorch提供的自动求导包,神经网络由权重、偏置等参数决定的函数构成,这些参数在PyTorch中都储存在 Tensor 里,神经网络的训练包括前向传播和反向传播两部分,前向传播就是用函数计算预测值,反向传播通过预测值产生的 error/loss 更新参数(通过梯度下降的方式)。
神经网络的一轮训练:
前向传播:prediction = model(data)
反向传播:
(1)计算loss
(2)loss.backward()(autograd会在这一步计算参数的梯度,存在相应参数Tensor的grad属性中)
(3)更新参数
1)加载optimizer(通过torch.optim)
2)optimizer.step() 使用梯度下降更新参数(梯度来源自参数的grad属性)
Tensor的requires_grad属性设为False,可以将其排除在DAG之外,autograd就不会计算它的梯度。在神经网络中,不需要计算梯度的参数叫frozen parameters。可以冻结不需要知道梯度的参数(节省计算资源),也可以在微调预训练模型时使用(此时往往冻结绝大多数参数,仅调整classifier layer参数,以在新标签上做预测),类似功能也用于 torch.no_grad() 的实现。
Neutral Network
- 神经网络可以通过torch.nn包搭建(torch.nn 预定义的层调用 torch.nn.functional包的函数)
- nn.Module包含了网络层
- forward(input)方法返回输出结果
网络训练流程:
(1)前向传播
(2)计算loss
(3)计算梯度
(4)使用梯度下降法更新参数
模型的可学习参数存储在model.parameters()中,其返回值是一个迭代器,包含模型及其所有子模型的参数。
定义网络:只需要定义forward() 方法,backward()方法会自动定义(用 autograd),在forward()方法中可以进行任何 Tensor 操作。
前向传播:out = net(input)
反向传播:先将参数梯度缓冲池清零(否则梯度会累加),再反向传播(此处使用一个随机矩阵),model.zero_grad(),out.backward(torch.randn(1, 10)),如果有计算出损失函数,上一行代码应为:loss.backward()
注意:torch.nn只支持mini-batch,如果只有一个输入数据可用 input.unsqueeze(0) 创造一个伪batch维度。
损失函数torch.nn包中定义的损失函数文档:Page Redirection
以MSELoss为例:criterion = nn.MSELoss(),loss = criterion(output, target)
得到的loss,其grad_fn组成的DAG:
调用loss.backward()后,所有张量的梯度都会得到更新:
print(loss.grad_fn) # MSELoss print(loss.grad_fn.next_functions[0][0]) # Linear print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # ReLU
多GPU并行训练
设置使用GPU:device = 'cuda' if torch.cuda.is_avaiable() else 'cpu'
DataParallel 和 DistributedDataParallel 两个类可用于GPU并行;
以 DataParallel 为例:model = nn.DataParallel(model)
在单卡上写好的 model 直接调用,别的都跟单卡形式一样,程序会自动把数据拆分放到所有已知的GPU上来运行,数据是直接从第一维拆开平均放到各个GPU上,相当于每个GPU放 batch_size / gpu_num 个样本。设置已知的GPU,可以在运行代码的 python 加上 CUDA_VISIBLE_DEVICES 参数,CUDA_VISIBLE_DEVICES=0,1,2,3 python example.py,如果要使用nohup的话,参数要加在nohup的前面,CUDA_VISIBLE_DEVICES=0,1,2,3 nohup python -u example.py >> nohup_output.log 2;如果不设置则默认为所有GPU,对GPU数量计数:torch.cuda.device_count() 代码。直接用 DataParallel 可能导致各卡空间不均衡的问题,建议使用 DistributedDataParallel。
Torch.squeeze()
Squeeze() 中如果没有指定删除的维度,则会将维度为 1 的都删除。
arr = np.array([1, 2, 3])
arr = arr.reshape(-1, 3, 1)
ta = torch.tensor(arr)ta = torch.tensor(arr)
ta.squeeze() -> tensor([1, 2, 3], dtype=torch.int32)
ta.squeeze(-1) -> tensor([[1, 2, 3]], dtype=torch.int32)
网络报错
RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation: [torch.FloatTensor [8, 250, 7]], which is output 0 of ReluBackward0, is at version 1; expected version 0 instead.
可使用下面的方法追踪问题:
import torch.autograd as autograd # set anomaly detection mode autograd.set_detect_anomaly(True)
需要更新的参数被原地操作更改了,可能是以下几种情况导致
1)找到网络模型中的 inplace 操作,将inplace=True改成 inplace=False,例如torch.nn.ReLU(inplace=False)
2)将代码中的“a+=b”之类的操作改为“c = a + b”,a=b改成a=ab,a/=b改成a=a/b。
3)训练代码的optimizer.step()函数放到loss.backward()后面
4)pytorch降版本到1.4或1.5
参考:
60分钟闪击速成PyTorch(Deep Learning with PyTorch: A 60 Minute Blitz)学习笔记-阿里云开发者社区