常用代码
1.张量拼接
‘’’
注意torch.cat和torch.stack的区别在于torch.cat沿着给定的维度拼接,
而torch.stack会新增一维。例如当参数是3个10x5的张量,torch.cat的结果是30x5的张量,
而torch.stack的结果是3x10x5的张量。
‘’’
tensor = torch.cat(list_of_tensors, dim=0)
tensor = torch.stack(list_of_tensors, dim=0)
2.one-hot 向量
pytorch的标记默认从0开始
tensor = torch.tensor([0, 2, 1, 3])
N = tensor.size(0)
num_classes = 4
one_hot = torch.zeros(N, num_classes).long()
one_hot.scatter_(dim=1, index=torch.unsqueeze(tensor, dim=1), src=torch.ones(N, num_classes).long())
3. 非零元素
torch.nonzero(tensor) # index of non-zero elements
torch.nonzero(tensor==0) # index of zero elements
torch.nonzero(tensor).size(0) # number of non-zero elements
torch.nonzero(tensor == 0).size(0) # number of zero elements
4. 判断两个张量相等
torch.allclose(tensor1, tensor2) # float tensor
torch.equal(tensor1, tensor2) # int tensor
5. 矩阵乘法
Matrix multiplcation: (m*n) * (n*p) * -> (m*p).
result = torch.mm(tensor1, tensor2)
Batch matrix multiplication: (b*m*n) * (b*n*p) -> (b*m*p)
result = torch.bmm(tensor1, tensor2)
6.水平翻转
tensor = tensor[:,:,:,torch.arange(tensor.size(3) - 1, -1, -1).long()]
7.模型权重初始化
注意 model.modules() 和 model.children() 的区别:model.modules() 会迭代地遍历模型的所有子层,而 model.children() 只会遍历模型下的一层。
#Common practise for initialization.
for layer in model.modules():
if isinstance(layer, torch.nn.Conv2d):
torch.nn.init.kaiming_normal_(layer.weight, mode='fan_out',
nonlinearity='relu')
if layer.bias is not None:
torch.nn.init.constant_(layer.bias, val=0.0)
elif isinstance(layer, torch.nn.BatchNorm2d):
torch.nn.init.constant_(layer.weight, val=1.0)
torch.nn.init.constant_(layer.bias, val=0.0)
elif isinstance(layer, torch.nn.Linear):
torch.nn.init.xavier_normal_(layer.weight)
if layer.bias is not None:
torch.nn.init.constant_(layer.bias, val=0.0)
8. 将在 GPU 保存的模型加载到 CPU
model.load_state_dict(torch.load('model.pth', map_location='cpu'))
9.modules and children
modules()会返回模型中所有模块的迭代器,它能够访问到最内层,比如self.layer1.conv1这个模块,还有一个与它们相对应的是name_children()属性以及named_modules(),这两个不仅会返回模块的迭代器,还会返回网络层的名字。
#取模型中的前两层
new_model = nn.Sequential(*list(model.children())[:2]
#如果希望提取出模型中的所有卷积层,可以像下面这样操作:
for layer in model.named_modules():
if isinstance(layer[1],nn.Conv2d):
conv_model.add_module(layer[0],layer[1])
#Initialization with given tensor.
layer.weight = torch.nn.Parameter(tensor)
10.导入另一个模型的相同部分到新的模型
模型导入参数时,如果两个模型结构不一致,则直接导入参数会报错。用下面方法可以把另一个模型的相同的部分导入到新的模型中。
#model_new代表新的模型
#model_saved代表其他模型,比如用torch.load导入的已保存的模型
model_new_dict = model_new.state_dict()
model_common_dict = {k:v for k, v in model_saved.items() if k in model_new_dict.keys()}
model_new_dict.update(model_common_dict)
model_new.load_state_dict(model_new_dict)
11.Labels moothing
for images, labels in train_loader:
images, labels = images.cuda(), labels.cuda()
N = labels.size(0)
# C is the number of classes.
smoothed_labels = torch.full(size=(N, C), fill_value=0.1 / (C - 1)).cuda()
smoothed_labels.scatter_(dim=1, index=torch.unsqueeze(labels, dim=1), value=0.9)
score = model(images)
log_prob = torch.nn.functional.log_softmax(score, dim=1)
loss = -torch.sum(log_prob * smoothed_labels) / N
edition2:
nll_loss和ce_loss之间的区别, 两者都是计算交叉熵, 但是nllloss 在输入之前已经做了log_softmax的操作。ce_loss 输入还是logits
lprobs = torch.log_softmax(logits, axis = -1)
lprobs = lprobs.view(-1, lprobs.size(-1)) #shape = (batch_size* seq_len, num_class)
nll_loss = -lprobs.gather(dim = 1, index = tgt_lab.view(-1,1))
smooth_loss = lprobs.sum(dim = -1, keep_dim = True)
epi = (1-smooth_lab)/lprobs.size(-1)
ce_loss = smooth_lab*nll_loss + epi*smooth_loss #batch_size * seq_len,1
ce_loss = (ce_loss*tgt_mask.view(-1,1)).sum()/tgt_mask.sum()
12. 梯度裁剪(gradient clipping)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=20)
13. 修改学习率
得到当前学习率
#If there is one global learning rate (which is the common case).
lr = next(iter(optimizer.param_groups))['lr']
另一种方法,在一个batch训练代码里,当前的lr是optimizer.param_groups[0][‘lr’]
#if there are multiple learning rates for different layers.
all_lr = []
for param_group in optimizer.param_groups:
all_lr.append(param_group['lr'])
14.保存模型与加载模型
start_epoch = 0
#Load checkpoint.
if resume: # resume为参数,第一次训练时设为0,中断再训练时设为1
model_path = os.path.join('model', 'best_checkpoint.pth.tar')
assert os.path.isfile(model_path)
checkpoint = torch.load(model_path)
best_acc = checkpoint['best_acc']
start_epoch = checkpoint['epoch']
model.load_state_dict(checkpoint['model'])
optimizer.load_state_dict(checkpoint['optimizer'])
print('Load checkpoint at epoch {}.'.format(start_epoch))
print('Best accuracy so far {}.'.format(best_acc))
#Train the model
for epoch in range(start_epoch, num_epochs):
...
# Test the model
...
# save checkpoint
is_best = current_acc > best_acc
best_acc = max(current_acc, best_acc)
checkpoint = {
'best_acc': best_acc,
'epoch': epoch + 1,
'model': model.state_dict(),
'optimizer': optimizer.state_dict(),
}
model_path = os.path.join('model', 'checkpoint.pth.tar')
best_model_path = os.path.join('model', 'best_checkpoint.pth.tar')
torch.save(checkpoint, model_path)
if is_best:
shutil.copy(model_path, best_model_path)
14. 分布式
torch.distributed.broadcast(tensor, src, group=group, async_op=False) # 将tensor从src(rank)广播到group中
torch.distributed.all_reduce(tensor, op=ReduceOp.SUM, group=group, async_op=False) # 对tensor进行原地in-place的reduce,op是torch.distributed.ReduceOp中的一个,指定了某种确定的element-wise的操作
torch.distributed.reduce(tensor, dst, op=ReduceOp.SUM, group=<object object>, async_op=False)
torch.distributed.all_gather(tensor_list, tensor, group=<object object>, async_op=False) # 将group中的tensor集中到tensor_list中
torch.distributed.gather(tensor, gather_list, dst, group=<object object>, async_op=False) # 将group中的tensor集中到dst(rank)处
torch.distributed.scatter(tensor, scatter_list, src, group=<object object>, async_op=False)
torch.distributed.barrier(group=<object object>, async_op=False)
Element-wise multiplication.
result = tensor1 * tensor2
部分函数用法
1. scatter_
scatter(dim, index, src) 的参数有 3 个
dim:沿着哪个维度进行索引
index:用来 scatter 的元素索引
src:用来 scatter 的源元素,可以是一个标量或一个张量
这个 scatter 可以理解成放置元素或者修改元素
简单说就是通过一个张量 src 来修改另一个张量,哪个元素需要修改、用 src 中的哪个元素来修改由 dim 和 index 决定
官方文档给出了 3维张量 的具体操作说明,如下所示
self[index[i][j][k]][j][k] = src[i][j][k] # if dim == 0
self[i][index[i][j][k]][k] = src[i][j][k] # if dim == 1
self[i][j][index[i][j][k]] = src[i][j][k] # if dim == 2
2. gather
torch.gather(input, dim, index, out=None, sparse_grad=False) → Tensor
output 和index 的·维度一致
out[i][j][k] = input[index[i][j][k]][j][k] # if dim == 0
out[i][j][k] = input[i][index[i][j][k]][k] # if dim == 1
out[i][j][k] = input[i][j][index[i][j][k]] # if dim == 2
t = torch.tensor([[1,2],[3,4]])
torch.gather(t, 1, torch.tensor([[0,0],[1,0]]))
tensor([[ 1, 1], [ 4, 3]])
实例:target dim = (BxT) output dim = (BxTxV) 求celoss 的时候, torch.scatter(output, dim = -1, index = target
3.tf.masked_select
t = torch.randint(0,9, size = (3,3))
mask = t.ge(5)
t_select = torch.masked_select(t, mask)
只保留True 的值, 返回一维向量。tensor([5, 5, 8, 5, 5, 7]
4.torch.autograd.grad
5. 模型保存与加载
X = torch.randn(2, 3)
Y = net(X) # 这个net就是上面创建的那个对象,我们把它的参数保存起来,然后新建一个net2,然后把保存的这些参数加载进net2,这样我们把X输入net2得到的Y2应该与Y是相等的
PATH = "./net.pt"
torch.save(net.state_dict(), PATH)
net2 = MLP()
net2.load_state_dict(torch.load(PATH))
Y2 = net2(X)
Y2 == Y
6.损失函数
- CrossEntropy loss
计算NLLloss 需提前对logits 做logsoftmax 操作,再和label一起输入。 - KLD Loss
*triplet loss
这个意思是:想让Anchor与Positive之间的距离<Anchor与Negative之间的距离
只有Anchor与Positive之间的距离比Anchor与Negative之间的距离小margin以上时,才不会产生损失。
7. 修改学习率
for param_group, lr in zip(self.optimizer.param_groups, self.get_lr()):
param_group['lr'] = lr
8. nn.Module 类的使用
import torch.nn as nn
from collections import OrderedDict
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.conv_block = nn.Sequential(
nn.Conv2d(3, 32, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(2))
self.dense_block = nn.Sequential(
nn.Linear(32 * 3 * 3, 128),
nn.ReLU(),
nn.Linear(128, 10)
) #这么定义这里在每一个包装块里面,各个层是没有名称的,默认按照0、1、2、3、4来排名。
'''
self.conv_block = nn.Sequential(
OrderedDict(
[
("conv1", nn.Conv2d(3, 32, 3, 1, 1)),
("relu1", nn.ReLU()),
("pool", nn.MaxPool2d(2))
]
))
self.dense_block = nn.Sequential(
OrderedDict([
("dense1", nn.Linear(32 * 3 * 3, 128)),
("relu2", nn.ReLU()),
("dense2", nn.Linear(128, 10))
])
)
'''
'''
self.conv_block.add_module("conv1",torch.nn.Conv2d(3, 32, 3, 1, 1))
self.conv_block.add_module("relu1",torch.nn.ReLU())
self.conv_block.add_module("pool1",torch.nn.MaxPool2d(2))
self.dense_block = torch.nn.Sequential()
self.dense_block.add_module("dense1",torch.nn.Linear(32 * 3 * 3, 128))
self.dense_block.add_module("relu2",torch.nn.ReLU())
self.dense_block.add_module("dense2",torch.nn.Linear(128, 10))
'''
def forward(self, x):
conv_out = self.conv_block(x)
res = conv_out.view(conv_out.size(0), -1)
out = self.dense_block(res)
return out
model = MyNet()
print(model)
9. model.children 和model.mudules()
(1)model.children()和model.named_children()方法返回的是迭代器iterator;
(2)model.children():每一次迭代返回的每一个元素实际上是 Sequential 类型,而Sequential类型又可以使用下标index索引来获取每一个Sequenrial 里面的具体层,比如conv层、dense层等;
(3)model.named_children():每一次迭代返回的每一个元素实际上是 一个元组类型,元组的第一个元素是名称,第二个元素就是对应的层或者是Sequential。
1)model.modules()和model.named_modules()方法返回的是迭代器iterator;
(2)model的modules()方法和named_modules()方法都会将整个模型的所有构成(包括包装层、单独的层、自定义层等)由浅入深依次遍历出来,只不过modules()返回的每一个元素是直接返回的层对象本身,而named_modules()返回的每一个元素是一个元组,第一个元素是名称,第二个元素才是层对象本身。
(3)如何理解children和modules之间的这种差异性。注意pytorch里面不管是模型、层、激活函数、损失函数都可以当成是Module的拓展,所以modules和named_modules会层层迭代,由浅入深,将每一个自定义块block、然后block里面的每一个层都当成是module来迭代。而children就比较直观,就表示的是所谓的“孩子”,所以没有层层迭代深入。
10. log-sum-exp(lse) and log_softmax
11. 张量拷贝
clone()
a = torch.tensor(1.0, requires_grad = True); b= a.clone()
b与a不共享内存,但提供梯度的回溯, 但不支持梯度的更新。
detach()
共享数据内存,可梯度更新,但不提供梯度的回溯
new_tensor()
不共享,不提供梯度回溯,支持梯度更新。
.data
与detach相同