PyTorch Trick集锦

PyTorch Trick集锦

  • -1、Dataset与DataLoader
    • 1、使用自带数据集
    • 2、自建数据集
  • 0、计算模型Param,与FLOPS
  • 1、多卡训练(指定GPU编号)
  • 2、查看模型每层输出
  • 3、梯度裁剪(Gradient Clipping)
  • 4、扩展tensor维度
  • 5、独热编码
  • 6、防止验证模型时爆显存
  • 7、学习率衰减
  • 8、冻结某些层的参数
    • 8-1
    • 8-2 .快速版(vgg16为例)
    • 8-3 .细调版
  • 9、对不同层使用不同学习率
  • 10、模型相关操作(权重文件裁剪)
  • 11、网络参数初始化
  • 12、加载内置预训练模型
  • 13、其他常用函数
    • 1 CPU 与GPU转换
    • 2 Register Buffer
    • 3 向量距离
    • 4 Normoalize 归一化
    • 5 线性层 + 分块技巧 (torch.chunk)
    • 6 Masked 筛选张量
    • 7 torch.where
    • 8 Tensor.scatter
    • 9 图像插值 (F.interpolate)
    • 10 图像做成网格 (torchvision.utils.make_grid)
    • 14 权重文件裁减
    • 15 sklearn做聚类
      • 1. K-means聚类
      • 2. **Mean Shift算法**

-1、Dataset与DataLoader

1、使用自带数据集

PyTorch提供的torch.utils.data.DataLoadertorch.utils.data.Dataset允许你使用预下载的数据集或自己制作的数据。Dataset用于存储样本及其相应的标签,而DataLoader能为数据集提供一个迭代器,以便于访问样本。

下面提供一个自带的Fashion
MNIST数据集,包括60000个训练样本和10000个测试样本。每个示例包括一个28×28灰度图像(特征图)和10个类别之一的标签。

import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor,Lambda
import matplotlib.pyplot as plt
import numpy as np

training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor(),
    target_transform = Lambda(lambda y: torch.zeros(
    10, dtype=torch.float).scatter_(dim=0, index=torch.tensor(y), value=1))
)

test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor(),
    target_transform = Lambda(lambda y: torch.zeros(
    10, dtype=torch.float).scatter_(dim=0, index=torch.tensor(y), value=1))
)
# 执行时,会下载并解压训练、测试数据集到 data/FashionMNIST/raw 目录
## root是要存储训练/测试数据的路径
## train指定数据集为训练集或测试集,
## download=True表示如果在root无从获取数据集,则从网上下载。
## transform和target_transform分别指定特征图和标签数据类型变换

所有TorchVision数据集都有两个参数,其中transform用于修改特征图,target_transform用于修改标签。torchvision.transforms模块提供了几种常用的转换,如下文的ToTensor()、Lambda。

FashionMNIST的特征是PIL图像格式,标签是整数。对于训练,我们需要将特征规范化为张量(tensor),将标签用独热(one-hot)编码张量表示。为了进行这些转换,我们使用ToTensor和Lambda。

ToTensor()
ToSensor将PIL图像或NumPy ndarray转换为浮点张量(FloatTensor)。并图像的像素值在[限制在[0,1]范围内。

Lambda Transforms
Lambda转换应用任何用户定义的lambda函数。在这里,我们定义了一个函数来将整数转换为一个独热编码张量。它首先创建一个大小为class_num的零张量(数据集中标签的数量),并调用scatter_,它在标签y给定的索引上指定值为1。

2、自建数据集

有10张图片,5张假(用0标记)5张真(用1标记)

链接:https://pan.baidu.com/s/1xGm6IMhq8zBQYZZCwwf3Aw 提取码:1111
PyTorch Trick集锦_第1张图片
PyTorch Trick集锦_第2张图片


import torch
from torch.utils.data import Dataset, DataLoader
from torchvision.transforms import ToTensor, ToPILImage, Lambda
import matplotlib.pyplot as plt
import os
import pandas as pd
from PIL import Image

img_path = '.\zoro'
label_path = '.\label_zoro.xlsx'


class ZoroDataset(Dataset):
    def __init__(self, label_file, img_dir, transform=None, target_transform=None):
        # 读取标签文件
        self.labels = pd.read_excel(label_file)
        # 定义文件目录
        self.img_dir = img_dir
        # 定义transform
        self.transform = transform
        self.target_transform = target_transform

    def __len__(self):
        '''返回数据集中的样本数'''
        return len(self.labels)

    def __getitem__(self, index):
        '''获取数据的方法,会和Dataloader连用'''
        # 获取图片路径,0表示Excel文件的第一列
        img_path = os.path.join(self.img_dir, self.labels.iloc[index, 0])
        # 读取图片
        image = Image.open(img_path)
        # 获取图片对应的标签,1表示Excel文件的第二列
        y_label = int(self.labels.iloc[index, 1])
        # 如果使用时附加了transform参数,则对图片应用转换
        if self.transform:
            image = self.transform(image)
        if self.target_transform:
            y_label = self.target_transform(y_label)
        return image, y_label


def img_show(img):
    '''将img转化为PIL图像格式后展示'''
    to_pil_image = ToPILImage()
    img = to_pil_image(img)
    plt.imshow(img)


# 这里标签采用one-hot编码只是为了展示效果,不考虑实际意义
dataset = ZoroDataset(label_file=label_path, img_dir=img_path, transform=ToTensor(),
                      target_transform=Lambda(lambda y: torch.zeros(
                          2, dtype=torch.float).scatter_(dim=0, index=torch.tensor(y), value=1)))

train_features, train_labels = dataset[0]
# img_show(train_features)
# print('one-hot标签:', train_labels)
# plt.show()
batch_size = 2
train_dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False)
print('数据集样本数:', len(dataset))

for epoch in range(2):
    data_iter = iter(train_dataloader)
    for i, (train_features, train_labels) in enumerate(data_iter):
        plt.figure(i)
        for j in range(batch_size):
            plt.subplot(int(f"1{batch_size}{j + 1}"))
            # print(train_features.size())
            img = train_features[j]
            label = train_labels[j]
            img_show(img)
            print(f"Label: {label}")
        plt.show()

迭代和可视化数据集

labels_map = {
    0: "T-Shirt",
    1: "Trouser",
    2: "Pullover",
    3: "Dress",
    4: "Coat",
    5: "Sandal",
    6: "Shirt",
    7: "Sneaker",
    8: "Bag",
    9: "Ankle Boot",
}
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
for i in range(1, cols * rows + 1):
    sample_idx = torch.randint(len(training_data), size=(1,)).item()
    img, label = training_data[sample_idx]
    figure.add_subplot(rows, cols, i)
    print('one-hot:',label)
    index=torch.nonzero(label)[0][0]   # one-hot转整数标签
    plt.title(labels_map[index.item()])
    plt.axis("off")
    plt.imshow(img.squeeze(), cmap="gray")
plt.show()

PyTorch Trick集锦_第3张图片

0、计算模型Param,与FLOPS

安装thop库: pip install thop

from torchvision.models import resnet50
from thop import profile

model = resnet50()
input = torch.randn(1, 3, 224, 224)
flops, params = profile(model, inputs=(input, ))

# 另一种计算方式
total = sum([param.nelement() for param in self.net.parameters()])

1、多卡训练(指定GPU编号)

设置当前使用的GPU设备仅为0号设备,设备名称为 /gpu:0:

os.environ["CUDA_VISIBLE_DEVICES"] = "0"

设置当前使用的GPU设备为0,1号两个设备,名称依次为 /gpu:0、/gpu:1:

os.environ["CUDA_VISIBLE_DEVICES"] = "0,1" 

根据顺序表示优先使用0号设备,然后使用1号设备。
指定GPU的命令需要放在和神经网络相关的一系列操作的前面。
多GPU运算分为单机多卡和多机多卡,两者在pytorch上面的实现并不相同,因为多机时,需要多个机器之间的通信协议等设置。

pytorch实现单机多卡十分容易,其基本原理就是:加入我们一次性读入一个batch的数据, 其大小为[16, 10, 5],我们有四张卡可以使用。那么计算过程遵循以下步骤:

1.假设我们有4个GPU可以用,pytorch先把模型同步放到4个GPU中。
2.那么首先将数据分为4份,按照次序放置到四个GPU的模型中,每一份大小为[4, 10, 5];   
3. 每个GPU分别进行前项计算过程;
4.前向过程计算完后,pytorch再从四个GPU中收集计算后的结果假设[4, 10, 5],然后再按照次序将其拼接起来[16, 10, 5],计算loss。
整个过程其实就是 同步模型参数→分别前向计算→计算损失→梯度反传
import torch
import torch.nn as nn

model = Model()
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01, momentum=0.9)
# 假设就一个数据
data = torch.rand([16, 10, 5])

# 前向计算要求数据都放进GPU0里面
# device = torch.device('cuda:0')
# data = data.to(device)
data = data.cuda()

# 将网络同步到多个GPU中
model_p = torch.nn.DataParalle(model.cuda(), device_ids=[0, 1,  2, 3])
logits = model_p(inputs)
  
# 接下来计算loss
loss = crit(logits, target)
optimizer.zero_grad()
loss.backward()
optimizer.step()

2、查看模型每层输出

Keras有一个简洁的API来查看模型的每一层输出尺寸,这在调试网络时非常有用。现在在PyTorch中也可以实现这个功能。
使用很简单,如下用法:

from torchsummary import summary
summary(your_model, input_size=(channels, H, W))

input_size 是根据你自己的网络模型的输入尺寸进行设置。

3、梯度裁剪(Gradient Clipping)

import torch.nn as nn

outputs = model(data)
loss= loss_fn(outputs, target)
optimizer.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), max_norm=20, norm_type=2)
optimizer.step()

nn.utils.clip_grad_norm_ 的参数:
parameters – 一个基于变量的迭代器,会进行梯度归一化
max_norm – 梯度的最大范数
norm_type – 规定范数的类型,默认为L2

nn.utils.clip_grad_value_(net.linear.weight, clip_value=1.1)
print("grad after clip:"+str(net.linear.weight.grad))
# grad after clip:tensor([[1.1000]])

得到的输出和预期相同,在clip之前梯度为120,在clip之后梯度为1.1:
@不椭的椭圆 提出:梯度裁剪在某些任务上会额外消耗大量的计算时间,可移步评论区查看详情。

4、扩展tensor维度

因为在训练时的数据维度一般都是 (batch_size, c, h, w),而在测试时只输入一张图片,所以需要扩展维度,扩展维度有多个方法:

import cv2
import torch

image = cv2.imread(img_path)
image = torch.tensor(image)
print(image.size())

img = image.unsqueeze(dim=0)  
print(img.size())

img = img.squeeze(dim=0)
print(img.size())

tensor.unsqueeze(dim):扩展维度,dim指定扩展哪个维度。
tensor.squeeze(dim):去除dim指定的且size为1的维度,维度大于1时,squeeze()不起作用,不指定dim时,去除所有size为1的维度。或

import cv2
import torch

image = cv2.imread(img_path)
image = torch.tensor(image)
print(image.size())

img = image.view(1, *image.size())
print(img.size())

# output:
# torch.Size([h, w, c])
# torch.Size([1, h, w, c])

import cv2
import numpy as np

image = cv2.imread(img_path)
print(image.shape)
img = image[np.newaxis, :, :, :]
print(img.shape)

# output:
# (h, w, c)
# (1, h, w, c)

5、独热编码

1.Pytorch内置one_hot函数
将Pytorch升级到1.2版本,试用了下 one_hot 函数,确实很方便。直接用torch.nn.functional.one_hot。

import torch.nn.functional as F
import torch

tensor =  torch.arange(0, 5) % 3  # tensor([0, 1, 2, 0, 1])
one_hot = F.one_hot(tensor)

# 输出:
# tensor([[1, 0, 0],
#         [0, 1, 0],
#         [0, 0, 1],
#         [1, 0, 0],
#         [0, 1, 0]])

也可以自己指定类别数:

tensor =  torch.arange(0, 5) % 3  # tensor([0, 1, 2, 0, 1])
one_hot = F.one_hot(tensor, num_classes=5)

# 输出:
# tensor([[1, 0, 0, 0, 0],
#         [0, 1, 0, 0, 0],
#         [0, 0, 1, 0, 0],
#         [1, 0, 0, 0, 0],
#         [0, 1, 0, 0, 0]])

升级 Pytorch (cpu版本)的命令:conda install pytorch torchvision -c pytorch

2.手动one-hot
在PyTorch中使用交叉熵损失函数的时候会自动把label转化成onehot,所以不用手动转化,而使用MSE需要手动转化成onehot编码。

import torch
class_num = 8
batch_size = 4

def one_hot(label):
    """
    将一维列表转换为独热编码
    """
    label = label.resize_(batch_size, 1)
    m_zeros = torch.zeros(batch_size, class_num)
    # 从 value 中取值,然后根据 dim 和 index 给相应位置赋值
    onehot = m_zeros.scatter_(1, label, 1)  # (dim,index,value)

    return onehot.numpy()  # Tensor -> Numpy

label = torch.LongTensor(batch_size).random_() % class_num  # 对随机数取余
print(one_hot(label))

# output:
[[0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0.]]

6、防止验证模型时爆显存

验证模型时不需要求导,即不需要梯度计算,关闭autograd,可以提高速度,节约内存。如果不关闭可能会爆显存。

with torch.no_grad():
    # 使用model进行预测的代码
    pass

torch.cuda.empty_cache() 的使用原因更新一下。
这是原回答:
Pytorch 训练时无用的临时变量可能会越来越多,导致 out of memory ,可以使用下面语句来清理这些不需要的变量。
官网 上的解释为:
Releases all unoccupied cached memory currently held by the caching allocator so that those can be used in other GPU application and visible innvidia-smi. torch.cuda.empty_cache()
意思就是PyTorch的缓存分配器会事先分配一些固定的显存,即使实际上tensors并没有使用完这些显存,这些显存也不能被其他应用使用。这个分配过程由第一次CUDA内存访问触发的。
而 torch.cuda.empty_cache() 的作用就是释放缓存分配器当前持有的且未占用的缓存显存,以便这些显存可以被其他GPU应用程序中使用,并且通过 nvidia-smi命令可见。注意使用此命令不会释放tensors占用的显存。
对于不用的数据变量,Pytorch 可以自动进行回收从而释放相应的显存。
更详细的优化可以查看 优化显存使用 和 显存利用问题。

7、学习率衰减


import torch.optim as optim
from torch.optim import lr_scheduler

# 训练前的初始化
optimizer = optim.Adam(net.parameters(), lr=0.001)
scheduler = lr_scheduler.StepLR(optimizer, 10, 0.1)  # # 每过10个epoch,学习率乘以0.1

# 训练过程中
for n in n_epoch:
    scheduler.step()
    ...

可以随时查看学习率的值:optimizer.param_groups[0][‘lr’]。
还有其他学习率更新的方式:
1、自定义更新公式:
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda epoch:1/(epoch+1))
2、不依赖epoch更新学习率:
lr_scheduler.ReduceLROnPlateau()
提供了基于训练中某些测量值使学习率动态下降的方法,它的参数说明到处都可以查到。
提醒一点就是参数 mode=‘min’ 还是’max’,取决于优化的的损失还是准确率,即使用
scheduler.step(loss)还是scheduler.step(acc) 。

8、冻结某些层的参数

参考:https://www.zhihu.com/question/311095447/answer/589307812

8-1

:在加载预训练模型的时候,我们有时想冻结前面几层,使其参数在训练过程中不发生变化。
我们需要先知道每一层的名字,通过如下代码打印:


net = Network()  # 获取自定义网络结构
for name, value in net.named_parameters():
    print('name: {0},\t grad: {1}'.format(name, value.requires_grad))

假设前几层信息如下


name: cnn.VGG_16.convolution1_1.weight,	 grad: True
name: cnn.VGG_16.convolution1_1.bias,	 grad: True
name: cnn.VGG_16.convolution1_2.weight,	 grad: True
name: cnn.VGG_16.convolution1_2.bias,	 grad: True
name: cnn.VGG_16.convolution2_1.weight,	 grad: True
name: cnn.VGG_16.convolution2_1.bias,	 grad: True
name: cnn.VGG_16.convolution2_2.weight,	 grad: True
name: cnn.VGG_16.convolution2_2.bias,	 grad: True

然后我们定义一个要冻结的层的列表:


no_grad = [
    'cnn.VGG_16.convolution1_1.weight',
    'cnn.VGG_16.convolution1_1.bias',
    'cnn.VGG_16.convolution1_2.weight',
    'cnn.VGG_16.convolution1_2.bias'
]

冻结方法如下:


net = Net.CTPN()  # 获取网络结构
for name, value in net.named_parameters():
    if name in no_grad:
        value.requires_grad = False
    else:
        value.requires_grad = True

冻结后我们再打印每层的信息:

name: cnn.VGG_16.convolution1_1.weight,	 grad: False
name: cnn.VGG_16.convolution1_1.bias,	 grad: False
name: cnn.VGG_16.convolution1_2.weight,	 grad: False
name: cnn.VGG_16.convolution1_2.bias,	 grad: False
name: cnn.VGG_16.convolution2_1.weight,	 grad: True
name: cnn.VGG_16.convolution2_1.bias,	 grad: True
name: cnn.VGG_16.convolution2_2.weight,	 grad: True
name: cnn.VGG_16.convolution2_2.bias,	 grad: True

可以看到前两层的weight和bias的requires_grad都为False,表示它们不可训练。
最后在定义优化器时,只对requires_grad为True的层的参数进行更新。

optimizer = optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=0.01)

8-2 .快速版(vgg16为例)

# import torchvision.models as models
# import torch
# import torch.nn as nn

# class Net(nn.Module):
#     def __init__(self, model):
#         super(Net, self).__init__()
#         # -2表示去掉model的后两层
#         self.vgg_layer = nn.Sequential(*list(model.children())[:-2])
#         self.transion_layer = nn.ConvTranspose2d(2048, 2048, kernel_size=14, stride=3)
#         self.pool_layer = nn.MaxPool2d(32)
#         self.Linear_layer = nn.Linear(2048, 8)

#     def forward(self, x):
#         x = self.resnet_layer(x)
#         x = self.transion_layer(x)
#         x = self.pool_layer(x)
#         #将一个多行的Tensor,拼接成一行,-1指在不告诉函数有多少列
#         x = x.view(x.size(0), -1)
#         x = self.Linear_layer(x)
#         return x

# vgg = models.vgg16(pretrained=True)
# model = Net(vgg)

8-3 .细调版

import torchvision.models as models
import torch
import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo

class CNN(nn.Module):

# 加载model
resnet50 = models.resnet50(pretrained=True)
#3 4 6 3 分别表示layer1 2 3 4 中Bottleneck模块的数量。res101则为3 4 23 3 
cnn = CNN(Bottleneck, [3, 4, 6, 3])
# # 读取参数
pretrained_dict = resnet50.state_dict()
model_dict = cnn.state_dict()
# # 将pretrained_dict里不属于model_dict的键剔除掉
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
# # 更新现有的model_dict
model_dict.update(pretrained_dict)
# # 加载我们真正需要的state_dict
cnn.load_state_dict(model_dict)
# print(resnet50)
# print(cnn)

input = torch.rand(2,3,512,512)
out = cnn(input)

9、对不同层使用不同学习率

我们对模型的不同层使用不同的学习率。
还是使用这个模型作为例子:


net = Network()  # 获取自定义网络结构
for name, value in net.named_parameters():
    print('name: {}'.format(name))

# 输出:
# name: cnn.VGG_16.convolution1_1.weight
# name: cnn.VGG_16.convolution1_1.bias
# name: cnn.VGG_16.convolution1_2.weight
# name: cnn.VGG_16.convolution1_2.bias
# name: cnn.VGG_16.convolution2_1.weight
# name: cnn.VGG_16.convolution2_1.bias
# name: cnn.VGG_16.convolution2_2.weight
# name: cnn.VGG_16.convolution2_2.bias

对 convolution1 和 convolution2 设置不同的学习率,首先将它们分开,即放到不同的列表里:


conv1_params = []
conv2_params = []

for name, parms in net.named_parameters():
    if "convolution1" in name:
        conv1_params += [parms]
    else:
        conv2_params += [parms]

# 然后在优化器中进行如下操作:
optimizer = optim.Adam(
    [
        {"params": conv1_params, 'lr': 0.01},
        {"params": conv2_params, 'lr': 0.001},
    ],
    weight_decay=1e-3,
)

我们将模型划分为两部分,存放到一个列表里,每部分就对应上面的一个字典,在字典里设置不同的学习率。当这两部分有相同的其他参数时,就将该参数放到列表外面作为全局参数,如上面的weight_decay
也可以在列表外设置一个全局学习率,当各部分字典里设置了局部学习率时,就使用该学习率,否则就使用列表外的全局学习率。

10、模型相关操作(权重文件裁剪)

这个内容比较多,我写成了一篇文章:https://zhuanlan.zhihu.com/p/73893187
权重裁剪:有时只需要使用别人的一部分模型,又想加载这部分的预训练权重,就需要权重文件裁剪

import torch
from thop import profile
# 加载自己的模型
from swin0 import model,Trans_init_weights   
from collections import OrderedDict


trans= model(224,4)

# 载入原始权重文件
ckpt = torch.load('/home/xzz/桌面/cascade_mask_rcnn_swin_base_patch4_window7.pth')['state_dict']
swin_base_backbone = {}

# 先找到所需层数的序号,比如需要4--349层:
for i in range(4,349):
    keys = list(ckpt.keys())[i].replace('backbone.','')
    swin_base_backbone[keys] = ckpt[list(ckpt.keys())[i]]

# 生成新的权重
swin_base_tinydict = OrderedDict(swin_base_backbone)
# 载入新的权重
trans.load_state_dict(swin_base_tinydict)
# 保存新的权重
torch.save(swin_base_tinydict,'home/f/new.pth')


# ckpt2 = trans.state_dict()
# x = torch.rand(2,128,80,80)
# print('# generator parameters:', sum(param.numel() for param in trans.parameters()))
# out = trans(x)

11、网络参数初始化

神经网络的初始化是训练流程的重要基础环节,会对模型的性能、收敛性、收敛速度等产生重要的影响。
以下介绍两种常用的初始化操作。

(1) 对于一些更加灵活的初始化方法,可以借助numpy。
对于自定义的初始化方法,有时tensor的功能不如numpy强大灵活,故可以借助numpy实现初始化方法,再转换到tensor上使用。


for layer in net1.modules():
    if isinstance(layer, nn.Linear): # 判断是否是线性层
        param_shape = layer.weight.shape
        layer.weight.data = torch.from_numpy(np.random.normal(0, 0.5, size=param_shape)) 
        # 定义为均值为 0,方差为 0.5 的正态分布

(2) 使用pytorch内置的torch.nn.init方法。
(2-1)xavier的均匀分布
PyTorch Trick集锦_第4张图片

torch.nn.init.xavier_uniform_(tensor, gain=1)

也称为Glorot initialization。

>>> w = torch.empty(3, 5)
>>> nn.init.xavier_uniform_(w, gain=nn.init.calculate_gain('relu'))

(2-2) xavier正态分布
PyTorch Trick集锦_第5张图片

torch.nn.init.xavier_normal_(tensor, gain=1)

也称为Glorot initialization。
(2-3) kaiming均匀分布

torch.nn.init.kaiming_uniform_
 (tensor, a=0, mode='fan_in', nonlinearity='leaky_relu')

PyTorch Trick集锦_第6张图片
默认为fan_in模式,fan_in可以保持前向传播的权重方差的数量级,fan_out可以保持反向传播的权重方差的数量级。

>>> w = torch.empty(3, 5)
>>> nn.init.kaiming_uniform_(w, mode='fan_in', nonlinearity='relu')

(2-4) kaiming正态分布

torch.nn.init.kaiming_normal_
 (tensor, a=0, mode='fan_in', nonlinearity='leaky_relu')

PyTorch Trick集锦_第7张图片

w = torch.empty(3, 5)
 nn.init.kaiming_normal_(w, mode='fan_out', nonlinearity='relu')

范例:

for name, m in self.named_modules():
            if any(map(lambda x: isinstance(m, x), [nn.Linear, nn.Conv1d, nn.Conv2d])):
                nn.init.kaiming_uniform_(m.weight, mode='fan_in')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)

线性层实例:

class LinearNet(nn.Module):
    def __init__(self, features_in=1, features_out=1):
        super().__init__()
        self.linear = nn.Linear(features_in, features_out)
        self._init_weight()

    def forward(self, x):
        return self.linear(x)
#其中self._init_weight()函数负责将w初始化为1,并将b初始化为0:

def _init_weight(self):
    nn.init.constant_(self.linear.weight, val=1)
    nn.init.constant_(self.linear.bias, val=0)

12、加载内置预训练模型

torchvision.models模块的子模块中包含以下模型:
AlexNet
VGG
ResNet
SqueezeNet
DenseNet
导入这些模型的方法为:


import torchvision.models as models
resnet18 = models.resnet18()(pretrained=True)
alexnet = models.alexnet()
vgg16 = models.vgg16()

有一个很重要的参数为pretrained,默认为False,表示只导入模型的结构,其中的权重是随机初始化的。如果pretrained 为 True,表示导入的是在ImageNet数据集上预训练的模型。
更多的模型可以查看:https://pytorch-cn.readthedocs.io/zh/latest/torchvision/torchvision-models/

13、其他常用函数

1 CPU 与GPU转换

尽量少用 .to(device) ,用 zeros_like / ones_like 之类的代替

a = torch.zeros_like(b)
torch.ones_like()
torch.rand_like()
torch.randn_like()
torch.randint_like()
torch.empty_like()
torch.full_like()

2 Register Buffer

有时,模型或损失函数需要有预先设置的参数,并在调用forward时使用,例如,它可以是一个“权重”参数,它可以缩放损失或一些固定张量,它不会改变,但每次都使用。

class ModuleWithCustomValues(nn.Module):
    def __init__(self, weights, alpha):
        super().__init__()
        self.register_buffer("weights", torch.tensor(weights))
        self.register_buffer("alpha", torch.tensor(alpha))
    
    def forward(self, x):
        return x * self.weights + self.alpha

m = ModuleWithCustomValues(
    weights=[1.0, 2.0], alpha=1e-4
)
m(torch.tensor([1.23, 4.56]))
tensor([1.2301, 9.1201])

3 向量距离

两个张量之间的欧几里得距离 :torch.cdist


points1 = torch.rand(32)
points2 = torch.rand(42) 
torch.cdist(points1, points2, p=2.0)
tensor.size(34)

余弦相似度 : F.cosine_similarity

import torch.nn.functional as F
batch_of_vectors = torch.rand((4, 64))
similarity_matrix = F.cosine_similarity(batch_of_vectors.unsqueeze(1), batch_of_vectors.unsqueeze(0), dim=2)

similarity_matrix:   tensor.size(44)

4 Normoalize 归一化

batch= torch.rand((4, 64))
normalized_batch = F.normalize(batch, p=2.0, dim=1)

torch.Size([4, 64])

5 线性层 + 分块技巧 (torch.chunk)

创建单一的线性层,将输出分成N块(替代多个线性层)。这种方法通常会带来更高的性能,

d = 1024
batch = torch.rand((8, d))
layers = nn.Linear(d, 128, bias=False), nn.Linear(d, 128, bias=False), nn.Linear(d, 128, bias=False)
one_layer = nn.Linear(d, 128 * 3, bias=False)
%%timeit
o1 = layers[0](batch)
o2 = layers[1](batch)
o3 = layers[2](batch) 

289 µs ± 30.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
替代为

%%timeit
o1, o2, o3 = torch.chunk(one_layer(batch), 3, dim=1)

202 µs ± 8.09 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

6 Masked 筛选张量

传统方法使用:torch.masked_select

data = torch.rand((3, 3)).requires_grad_()
mask = data > data.mean()
torch.masked_select(data, mask)

nask:tensor([[False,  True,  True],
        [ True, False,  True],
        [False, False, False]])
tensor([0.7170, 0.7713, 0.9458, 0.6711], grad_fn=<MaskedSelectBackward>)

也可以直接用 data[mask]data * mask(补0)

data[mask]
tensor([0.7170, 0.7713, 0.9458, 0.6711], grad_fn=<IndexBackward>)


data * mask
tensor([[0.0000, 0.7170, 0.7713],
        [0.9458, 0.0000, 0.6711],
        [0.0000, 0.0000, 0.0000]], grad_fn=<MulBackward0>)

7 torch.where

把两个张量结合在一起:如果条件是真,那么从第一个张量中取元素,如果条件是假,从第二个张量中取元素。

x = torch.tensor([1, 2, 3, 4, 5], requires_grad=True)
y =torch.tensor([-1, -2, =3, =4, =5], requires_grad=True)
condition_or_mask = x <= 3.0
torch.where(condition_or_mask, x, y)
tensor([ 1,  2,  3, -4, -5], grad_fn=<SWhereBackward>)

8 Tensor.scatter

用给定位置的一个张量填充另一个张量。一维张量举例

data = torch.tensor([1, 2, 3, 4, 5])
index = torch.tensor([0, 1, 4])
values = torch.tensor([-1, -2, -3, -4, -5])
data.scatter(0, index, values)
tensor([-1, -2,  3,  4, -3])

二维张量举例:index的形状与values的形状相关,而index中的值对应于data中的位置。

data = torch.zeros((4, 4)).float()
index = torch.tensor([ [0, 1],
    [2, 3],
    [0, 3],
    [1, 2]])
values = torch.arange(1, 9).float().view(4, 2)
values, data.scatter(1, index, values)
(tensor([[1., 2.],
        [3., 4.],
        [5., 6.],
        [7., 8.]]),
tensor([[1., 2., 0., 0.],
        [0., 0., 3., 4.],
        [5., 0., 0., 6.],
        [0., 7., 8., 0.]]))

9 图像插值 (F.interpolate)

img = Image.open("./cat.jpg")
 F.interpolate(to_tensor(img).unsqueeze(0),  # batch of size 1
                  mode="bilinear", 
                  scale_factor=2.0, 
                  align_corners=False)

10 图像做成网格 (torchvision.utils.make_grid)

PyTorch Trick集锦_第8张图片

from torchvision.utils import make_grid
from torchvision.transforms.functional import to_tensor, to_pil_image
from PIL import Image
img = Image.open("./cat.jpg")
to_pil_image(
    make_grid(
        [to_tensor(i) for i in [img, img, img]],
         nrow=2, # number of images in single row
         padding=5 # "frame" size
     )
)

14 权重文件裁减

用来截取所需权重文件的一部分,并且修改keys名

import collections
import torch
ckpt = torch.load('/home/ubuntu/YOLOX-main/yolox_l_backbone.pth')
ckpt_keys = list(ckpt)
# ckpt_keys = list(ckpt['state_dict'])
new_dict = collections.OrderedDict()
num_layer = len(ckpt_keys)
# for i in range(321,349):
for i in range(0,num_layer):    
    # new_dict[ckpt_keys[i]] = ckpt['model'][ckpt_keys[i]]
    new_dict[ckpt_keys[i].replace('backbone.','')] = ckpt[ckpt_keys[i]]
    # new_dict[ckpt_keys[i][9:]] = ckpt['model'][ckpt_keys[i]]

torch.save(new_dict,'/home/ubuntu/YOLOX-main/yolox_l_backbone2.pth')

15 sklearn做聚类

1. K-means聚类

算法也称k均值聚类算法,它采用距离作为相似性的评价指标,即认为两个对象
的距离越近,其相似度就越大。具体步骤为:

① 首先确定一个k值,即我们希望将数据集经过聚类得到k个集合。
② 从数据集中随机选择 k个数据点作为质心。
③ 对数据集中每一个点,计算其与每一个质心的距离(如欧式距离),离哪个质心近,就划
分到那个质心所属的集合。
④ 把所有数据归好集合后,一共有k个集合。然后重新计算每个集合的质心(数据均值)。
⑤ 如果新计算出来的质心和原来的质心之间的距离小于某一个设置的阈值(表示重新计算的
质心的位置变化不大,趋于稳定,或者说收敛),我们可以认为聚类已经达到期望的结果,
算法终止。
⑥ 如果新质心和原质心距离变化很大,需要迭代3~5步骤。

import numpy as np
import matplotlib.pyplot as plt
from sklearn import metrics
from sklearn.cluster import KMeans

# 1. 函数:加载文件 
  # 可用data = load_data('data_multivar.txt')
def load_data(input_file):
    X = []
    with open(input_file,'r') as f:
    for line in f.readlines():
    data = [float(x) for x in line.split(',')]
    X.append(data)
return np.array(X)

# 3. 初始类簇4个
num_clusters = 4
# 4. 绘制
plt.figure()
plt.scatter(data[:,0], data[:,1],marker='o', facecolors='none', edgecolors='k', s=30)
x_min, x_max = min(data[:, 0]) - 1, max(data[:,0]) + 1
y_min, y_max = min(data[:, 1]) - 1, max(data[:,1]) + 1
plt.title('Input data')
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.xticks(())
plt.yticks(())
plt.show()

# 5. 训练模型
kmeans = KMeans(n_clusters=num_clusters)
kmeans.fit(data)
plt.scatter(data[:,0], data[:,1], marker='o', facecolors='none', edgecolors='k', s=30)
centroids = kmeans.cluster_centers_ # 获取质心
# 6. 绘图
plt.scatter(centroids[:,0], centroids[:,1], marker='o', s=200,linewidths=3, color='k', zorder=10, facecolors='black')
x_min, x_max = min(data[:, 0]) - 1, max(data[:, 0]) + 1
y_min, y_max = min(data[:, 1]) - 1, max(data[:, 1]) + 1
plt.title('Centoids and boundaries obtained using KMeans')
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.xticks(())
plt.yticks(())
plt.show()

PyTorch Trick集锦_第9张图片
(一)优点
1、原理比较简单,实现也是很容易,收敛速度快。
2、当结果簇是密集的,而簇与簇之间区别明显时, 它的效果较好。
3、主要需要调参的参数仅仅是簇数k。
(二)缺点
1、K值需要预先给定,很多情况下K值的估计是非常困难的。
2、K-Means算法对初始选取的质心点是敏感的,不同的随机种子点得到的聚类结果
完全不同 ,对结果影响很大。
3、对噪音和异常点比较的敏感。用来检测异常值。
4、采用迭代方法,可能只能得到局部的最优解,而无法得到全局的最优解。

为确定最佳k值,用评价分数遍历:

scores = []
range_values = np.arange(2, 10) # 初始聚类个数范围[2, 9]
for i in range_values:
    # 训练模型
    kmeans = KMeans(init='k-means++', n_clusters=i, n_init=10)
    kmeans.fit(data)
    score = metrics.silhouette_score(data, kmeans.labels_,metric='euclidean', sample_size=len(data))
    print ("\n聚类个数 =", i)
    print ("\n轮廓系数得分 =", score)
    scores.append(score)
    
# 绘制得分柱状图
plt.figure()
plt.bar(range_values, scores, width=0.6, color='k', align='center')
plt.title('Silhouette score')

PyTorch Trick集锦_第10张图片

2. Mean Shift算法

Mean Shift算法关键操作是通过感兴趣区域内的数据密度变化计算中心点的漂移向量,从
而移动中心点进行下一次迭代,直到到达密度最大处(中心点不变)。
从每个数据点出发都可以进行该操作,在这个过程,统计出现在感兴趣区域内的数据的次数。
该参数将在最后作为分类的依据。
PyTorch Trick集锦_第11张图片
算法实现:

① 在未被标记的数据点中随机选择一个点作为起始中心点center;
② 找出以center为中心半径为radius的区域中出现的所有数据点,认为这些点同属于一个聚类C。同时
在该聚类中记录数据点出现的次数加1。
③ 以center为中心点,计算从center开始到集合M中每个元素的向量,将这些向量相加,得到向量shift。
④ center = center + shift。即center沿着shift的方向移动,移动距离是||shift||。
⑤ 重复步骤2、3、4,直到shift的很小(就是迭代到收敛),记住此时的center。注意,这个迭代过程
中遇到的点都应该归类到簇C。
⑥ 如果收敛时当前簇C的center与其它已经存在的簇C2中心的距离小于阈值,那么把C2和C合并,数据
点出现次数也对应合并。否则,把C作为新的聚类。
⑦ 重复1、2、3、4、5直到所有的点都被标记为已访问。
⑧ 分类:根据每个类,对每个点的访问频率,取访问频率最大的那个类,作为当前点集的所属类。

import numpy as np
from sklearn.cluster import MeanShift
from sklearn.datasets import make_blobs

# 1 生成样本数据
X, _ = make_blobs(n_samples=500,cluster_std=0.6)

# 2 创建MeanShift对象
ms = MeanShift()
ms.fit(X)
labels = ms.labels_ 
cluster_centers = ms.cluster_centers_ 
print("质心:\n", cluster_centers)
labels_unique = np.unique(labels)
n_clusters_ = len(labels_unique)
print("不同的聚类数量 : %d \n" % n_clusters_)
# 3 绘图
import matplotlib.pyplot as plt
plt.scatter(X[:,0], X[:,1], marker='o', facecolors='none', edgecolors='k', s=30)
plt.scatter(cluster_centers[:,0], cluster_centers[:,1], marker='o', 
        s=150, linewidths=3, color='k', zorder=10, facecolors='blue')
x_min, x_max = min(X[:, 0]) - 1, max(X[:, 0]) + 1
y_min, y_max = min(X[:, 1]) - 1, max(X[:, 1]) + 1
plt.title('Centoids and boundaries obtained using KMeans')
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.xticks(())
plt.yticks(())
plt.show()

运行结果:
PyTorch Trick集锦_第12张图片
(一)优点
1、与K-Means算法不一样的是,Mean Shift算法可以自动决定类别的数目。
2、不受离异值影响。
3、没有局部最小值。
(二)缺点
1、在高维空间数据下表现不佳。
2、无法指定聚类的数量

def mean_shift(data, radius=2.5):
    clusters = []
    for i in range(len(data)):
    # 假设每个数据点为初始 聚类中心center
        cluster_centroid = data[i] # 初始center
        if i==0:
            print("旧的质心:",cluster_centroid)
        cluster_frequence = np.zeros(len(data)) # 初始每个数据点的聚类频率属性
        # 遍历数据点
        while True:
             temp_data = []
             for j in range(len(data)): # 每次都遍历所有元素
                 v = data[j] # 获取第j个点
                 # np.linalg.norm() 求范数,sqrt(x1 + x2+ x3 + ... + xn)
                 if np.linalg.norm(v - cluster_centroid)<= radius:
                    # 把半径内的所有数据集合起来
                    temp_data.append(v)
                    cluster_frequence[i] += 1 # 聚类中心记录数据点出现的次数加1
             # 旧的质心
             old_centroid = cluster_centroid
             # 新的质心,半径内所有向量的平均
             new_centroid = np.average(temp_data,axis=0)
             # 更新质心
             cluster_centroid = new_centroid
             # 如果新旧质心一致,则退出
            if np.array_equal(new_centroid,old_centroid):
            # 聚合相同的簇
               has_same_cluster = False
               for cluster in clusters:
                   # 两个质心小于半径,则为同一个质心
                   if np.linalg.norm(cluster['centroid'] - cluster_centroid) <= radius:
                      has_same_cluster = True
                      cluster['frequency'] = cluster['frequency'] + cluster_frequence
                      break
               # 如果质心不同,保存质心
               if not has_same_cluster:
                   clusters.append({
                   'centroid':cluster_centroid,
                   'frequency':cluster_frequence })
           # 输出粗的个数和值
           print("clusters (", len(clusters), '): ', clusters)
           print("新的质心:", cluster_centroid)
       # 根据频率聚类数据
       def clustering(data, clusters):
           t = []
           for cluster in clusters:
               cluster['data'] = []
               t.append(cluster['frequency'])
           t = np.array(t)
           # 聚类
           for i in range(len(data)):
               column_frequency = t[:, i]
               cluster_index = np.where(column_frequency == np.max(column_frequency))[0][0]
               clusters[cluster_index]['data'].append(data[i])

你可能感兴趣的:(pytorch,深度学习,神经网络)