文章目录
PyTorch常用函数、功能小结
张量
创建
torch.from_numpy(np_array) # 将numpy array转换为张量
torch.zeros_like(input_tensor) # 创建一个除了data不同其他基本一致的零张量
alpha_tensor = torch.tensor(self.alpha, device=self.device)
默认张量require_grad=False
维度
Understanding dimensions in PyTorch - https://towardsdatascience.com/understanding-dimensions-in-pytorch-6edf9972d3be
变形
a.t() // 矩阵转置
torch.transpose(mat, dim1, dim2) # 张量转置
torch.concat((a, b), 1) # 在列上拼接a, b张量
torch.squeeze(x, dim) # 增加维度
torch.unsqueeze(x, dim) # 此处dim 和x.dim()对应, with a dimension of size one inserted at the specified position
numpy.vstack([A, newrow])
pad_sequence(tensor_list)
运算
torch.matmul(a, b) // 张量乘法
torch.mm(a,b) # 矩阵乘法(无broadcasting)
torch.dot(a, b) # 张量点乘
torch.div(a, b) # 张量element-wise 除法
torch.mean(sentence_embeddings, 0)
a*b // 点乘
选择
a[rows] # 选择行
- 如果发现奇怪的选择值,需要检查index selector的数据类型。比如CUDA Tensor需要转换为int。
排序
max_values, argsorts = torch.sort(pred1, dim=1, descending=True) // 类似numpy.argsort
x.argsort() # torch.sort的第二个返回值
其他常用函数
F.normalize(input, p, dim)
CUDA计算相关
self.projector.to(torch.device("cuda:" + str(device))) #
t.detach().cpu().numpy() //将CUDA张量转换为numpy
CUDA-Out-of memory
- 最直接的方法是减少batch_size
- 查看GPU使用情况:
$ nvidia-smi
- 删除张量并释放显存
for x in batch_dict: // batch_dict contains CUDA tensors
del x
torch.cuda.empty_cache()
相关资料
https://zhuanlan.zhihu.com/p/85838274
模型
梯度传播
tensor.detach()
返回一个新的tensor,从当前计算图中分离下来的,但是仍指向原变量的存放位置,不同之处只是requires_grad为false,得到的这个tensor永远不需要计算其梯度,不具有grad。- 当优化器包含模型内所有参数时,
model.zero_grad()
等价于optim.zero_grad()
模型的基本组成
- submodule
Module可以包含其他多个module对象,submodule可以通过一般的属性赋值创建:
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.conv1 = nn.Conv2d(1, 20, 5)
self.conv2 = nn.Conv2d(20, 20, 5)
# 第二种方式:self.add_module()
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.add_module("conv1", Conv2d(3, 16, 5, padding=2))
- 查看模型的参数
for name, param in model.named_parameters():
if param.requires_grad:
print name, param.data
训练的基本组成
损失函数可以自定义:
def my_loss(output, target):
loss = torch.mean((output - target)**2)
return loss
model = nn.Linear(2, 2)
x = torch.randn(1, 2)
target = torch.randn(1, 2)
output = model(x)
loss = my_loss(output, target)
loss.backward()
print(model.weight.grad)
定义模型的优化器:
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
模型的状态
模型的某些层(Dropout, BatchNorm等)在train和eval下有不同的表现
model.train()
model.eval()
: 常常和torch.no_grad()
一起使用(如下)
# evaluate model:
model.eval()
with torch.no_grad():
...
out_data = model(data)
...
模型的训练
- 基本操作:
predictions = model(inputs) # Forward pass
loss = loss_function(predictions, labels) # Compute loss function
loss.backward() # Backward pass
optimizer.step() # Optimizer step
predictions = model(inputs) # Forward pass with new parameters
训练大型数据的技巧
梯度累加
受限于GPU显存的大小,大一些的数据集无法被一次性放入模型中训练。
model.zero_grad() # 清除累积梯度 # Reset gradients tensors
for i, (inputs, labels) in enumerate(training_set):
predictions = model(inputs) # Forward pass
loss = loss_function(predictions, labels) # Compute loss function
loss = loss / accumulation_steps # Normalize our loss (if averaged)
loss.backward() # Backward pass
if (i+1) % accumulation_steps == 0: # Wait for several backward steps
optimizer.step() # Now we can do an optimizer step
model.zero_grad() # Reset gradients tensors
if (i+1) % evaluation_steps == 0: # Evaluate the model when we...
evaluate_model() # ...have no gradients accumulated
还值得注意的是,为了经过 iter_size
次迭代后的累积梯度跟使用一个大的batch_size*iter_size
一样,需要将每次的 loss 除以 iter_size
。
模型的并行计算: DataParallel
if torch.cuda.device_count() > 1:
log.info("The model is running in parallel on {} GPUs.".format(torch.cuda.device_count()))
model = DataParallelWrapper(model)
注意此时原model里的参数方法无法被直接调用, 解决方法:
class MyDataParallel(nn.DataParallel):
def __getattr__(self, name):
return getattr(self.module, name)
模型的储存
torch.save(model.state_dict(), PATH)
CUDA 计算
- 将模型放入GPU:
device = torch.device("cuda:0")
model.to(device)
- 改变CUDA的默认GPU编号:
CUDA_VISIBLE_DEVICES=1 python3 LAN_test.py
PyTorch机制
动态图
loss.backward()
功能 计算tensor对于图中叶结点的梯度。
根据pytorch中backward()
函数的计算,当进行求导时,梯度是累积计算(累加)而不是被替换,但在处理每一个batch时并不需要与其他batch的梯度混合起来累积计算,因此需要对每个batch调用一遍zero_grad()将当前可变参数的梯度置0。
retain_graph
: 每次 backward() 时,默认会把整个计算图free掉。一般情况下是每次迭代,若在当前backward()
后,不执行forward() 而可以执行另一个backward(),需要在当前backward()
时,指定保留计算图,即backward(retain_graph)
。
PyTorch Hook 笔记
概念
挂钩式编程(hook programming)
Hook的常见作用
PyTorch中的两种Hook:
- 作用在张量上的hook
- 作用在module上的hook
例子:简单的计算图
我们先来创建一个非常简单的计算图:
a = torch.Tensor(2.0, requires_grad=True)
b = torch.Tensor(3.0, requires_grad=True)
c = a * b
a 和 b是叶节点,PyTorch默认只有叶结点会储存梯度。如果我们想储存c的梯度,需要对c使用c.retain_grad()
这个前向计算图会对应一个反向传播的图,如下图中的右边部分:
反向传播图不像用户自己定义的计算图(即前向传播图)那样,它难以直接获取数据。
作用在张量上的hook
重点:
- 张量上的hook函数是以OrderedDict形式储存起来的,因此顺序很重要——hook函数会按顺序被调用
- 如果hook函数返回值,那么这个返回值会修改该张量内的grad;如果不返回值,那么该张量内的grad不变
- 对于叶结点a, 如果调用了
a.retain_grad()
,return_grad_hook
会被register这个节点张量中。至于是储存hook改变之前还是之后的张量,取决于a.retain_grad()
调用的位置。 register_hook
会返回这个hook
的handler, 用来移除这个hook。- 如果在hook函数里直接改变作为参数的grad的值,也会影响其他节点。因此不可以对grad做in-place操作,比如
grad+=100
这种。
例子
# define the computational graph
a = torch.Tensor(2.0, requires_grad=True)
b = torch.Tensor(3.0, requires_grad=True)
c = a * b
d = torch.Tensor(4.0, requires_grad=True)
e = c * d
# define the hook function
def c_hook(grad):
print(grad)
return grad + 2
# register hooks
h = c.register_hook(c_hook)
c.register_hook(lambda grad: print(grad))
c.retain_grad()
d.register_hook(lambda grad: grad + 100)
e.retain_grad() #
e.register_hook(lambda grad: grad*2)
当我们想要移除某个hook时,
# remove hooks
h.remove()
作用在Module上的hook
这种hook分为:
- register_forward_hook
- register_forward_pre_hook
- register_backward_hook (not fixed), 推荐直接用tensor based hook
hook函数的定义
def module_forward_hook(module, positional_arguments):
...
def module_forward_pre_hook(module, positional_arguments, output):
...
def module_forward_hook(module, grad_input, grad_output):
...
如果forward(1,2, c=3)
,那么hook函数只会接收1,3
。
参考资料:
【PyTorch】PyTorch中的梯度累加
PyTorch 梯度累积小技巧
Pytorch的backward()相关理解
PyTorch Hooks Explained - In-depth Tutorial