张量如同数组和矩阵一样, 是一种特殊的数据结构。在PyTorch中, 神经网络的输入、输出以及网络的参数等数据, 都是使用张量来进行描述。
张量的使用和Numpy中的ndarrays很类似, 区别在于张量可以在GPU或其它专用硬件上运行, 这样可以得到更快的加速效果。
# 1.直接生成张量
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)# 2.Numpy数组来生成张量
np_array = np.array(data)
x_np = torch.from_numpy(np_array)# 3.已有的张量来生成新的张量
# 新的张量将继承已有张量的数据属性(结构、类型), 也可以重新指定新的数据类型x_ones = torch.ones_like(x_data) # 保留 x_data 的属性
print(f"Ones Tensor: \n {x_ones} \n")x_rand = torch.rand_like(x_data, dtype=torch.float) # 重写 x_data 的数据类型int -> float
print(f"Random Tensor: \n {x_rand} \n")
#显示
Ones Tensor:tensor([[1, 1],[1, 1]])Random Tensor:tensor([[0.0381, 0.5780],[0.3963, 0.0840]])# 4.通过指定数据维度来生成张量
shape = (2,3,) # shape是元组类型, 用来描述张量的维数
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")
Random Tensor:tensor([[0.0266, 0.0553, 0.9843],[0.0398, 0.8964, 0.3457]])Ones Tensor:tensor([[1., 1., 1.],[1., 1., 1.]])Zeros Tensor:tensor([[0., 0., 0.],[0., 0., 0.]])
从张量属性我们可以得到张量的维数、数据类型以及它们所存储的设备(CPU或GPU)。
tensor = torch.rand(3,4)print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")输出:
Shape of tensor: torch.Size([3, 4]) # 维数
Datatype of tensor: torch.float32 # 数据类型
Device tensor is stored on: cpu # 存储设备
有超过100种张量相关的运算操作, 例如转置、索引、切片、数学运算、线性代数、随机采样等
https://pytorch.org/docs/stable/torch.html
# 1.判断当前环境GPU是否可用, 然后将tensor导入GPU内运行
if torch.cuda.is_available():tensor = tensor.to('cuda')# 2.张量的索引和切片
tensor = torch.ones(4, 4)
tensor[:,1] = 0 # 将第1列(从0开始)的数据全部赋值为0
print(tensor)
# 输出:
tensor([[1., 0., 1., 1.],[1., 0., 1., 1.],[1., 0., 1., 1.],[1., 0., 1., 1.]])# 3.张量的拼接
t1 = torch.cat([tensor, tensor, tensor], dim=1)
print(t1)
tensor([[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.]])# 4.张量的乘积和矩阵乘法
# 逐个元素相乘结果
print(f"tensor.mul(tensor): \n {tensor.mul(tensor)} \n")
# 等价写法:
print(f"tensor * tensor: \n {tensor * tensor}")
tensor.mul(tensor):tensor([[1., 0., 1., 1.],[1., 0., 1., 1.],[1., 0., 1., 1.],[1., 0., 1., 1.]])tensor * tensor:tensor([[1., 0., 1., 1.],[1., 0., 1., 1.],[1., 0., 1., 1.],[1., 0., 1., 1.]])
# 张量与张量的矩阵乘法
print(f"tensor.matmul(tensor.T): \n {tensor.matmul(tensor.T)} \n")
# 等价写法:
print(f"tensor @ tensor.T: \n {tensor @ tensor.T}")
tensor.matmul(tensor.T):tensor([[3., 3., 3., 3.],[3., 3., 3., 3.],[3., 3., 3., 3.],[3., 3., 3., 3.]])tensor @ tensor.T:tensor([[3., 3., 3., 3.],[3., 3., 3., 3.],[3., 3., 3., 3.],[3., 3., 3., 3.]])
# 5.自动赋值运算
# 自动赋值运算通常在方法后有 _ 作为后缀, 例如: x.copy_(y), x.t_()操作会改变 x 的取值。
# 自动赋值运算虽然可以节省内存, 但在求导时会因为丢失了中间过程而导致一些问题, 所以我们并不鼓励使用它。
print(tensor, "\n")
tensor.add_(5)
print(tensor)
tensor([[1., 0., 1., 1.],[1., 0., 1., 1.],[1., 0., 1., 1.],[1., 0., 1., 1.]])tensor([[6., 5., 6., 6.],[6., 5., 6., 6.],[6., 5., 6., 6.],[6., 5., 6., 6.]])
张量和Numpy array数组在CPU上可以共用一块内存区域, 改变其中一个另一个也会随之改变。
# 1. 由张量变换为Numpy array数组
t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]# 修改张量的值,则Numpy array数组值也会随之改变
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]# 2.由Numpy array数组转为张量
n = np.ones(5)
t = torch.from_numpy(n)
# 修改Numpy array数组的值,则张量值也会随之改变。
np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]
https://pytorch.org/docs/stable/generated/torch.norm.html#torch.norm
torch.norm(input, p='fro', dim=None, keepdim=False, out=None, dtype=None)1
#
feature = torch.cat(feature,-1)
torch.range(start=1, end=6) 的结果是会包含end的,
而torch.arange(start=1, end=6)的结果并不包含end。
两者创建的tensor的类型也不一样
>>> y=torch.range(1,6)
>>> y
tensor([1., 2., 3., 4., 5., 6.])
>>> y.dtype
torch.float32>>> z=torch.arange(1,6)
>>> z
tensor([1, 2, 3, 4, 5])
>>> z.dtype
torch.int64
在pytorch中view函数的作用为重构张量的维度,相当于numpy中resize()的功能,但是用法可能不太一样
class Conv2d(_ConvNd):# 初始化函数,这里主要了解有哪些参数传进来就可以了def __init__(self,in_channels: int,out_channels: int,kernel_size: _size_2_t,stride: _size_2_t = 1,padding: _size_2_t = 0,dilation: _size_2_t = 1,groups: int = 1,bias: bool = True,padding_mode: str = 'zeros' # TODO: refine this type):#————————————————看到这就可以了————————————————————kernel_size = _pair(kernel_size)stride = _pair(stride)padding = _pair(padding)dilation = _pair(dilation)super(Conv2d, self).__init__(in_channels, out_channels, kernel_size, stride, padding, dilation,False, _pair(0), groups, bias, padding_mode)def _conv_forward(self, input, weight):if self.padding_mode != 'zeros':return F.conv2d(F.pad(input, self._reversed_padding_repeated_twice, mode=self.padding_mode),weight, self.bias, self.stride,_pair(0), self.dilation, self.groups)return F.conv2d(input, weight, self.bias, self.stride,self.padding, self.dilation, self.groups)# 前向传播计算def forward(self, input: Tensor) -> Tensor:return self._conv_forward(input, self.weight)
Conv2d是一个类,它包含了做卷积运算所需要的参数(__init__函数),以及卷积操作(forward函数)
一共九个参数,一般用前三个就可以处理一般的任务:
nn.linear()是用来设置网络中的全连接层的,而在全连接层中的输入与输出都是二维张量,一般形状为[batch_size, size],与卷积层要求输入输出是4维张量不同
CLASS torch.nn.Linear(in_feature,out_feature,bias=True)
- in_feature:
import torch
import torch.nn as nn
import torch.nn.functional as Fclass Net(nn.Module):def __init__(self):super(Net, self).__init__()# 1 input image channel, 6 output channels, 5x5 square convolution# kernel# batch_size,输入通道数,图像高宽self.conv1 = nn.Conv2d(1, 6, 5)self.conv2 = nn.Conv2d(6, 16, 5)# an affine operation: y = Wx + bself.fc1 = nn.Linear(16 * 5 * 5, 120) # 5*5 from image dimensionself.fc2 = nn.Linear(120, 84)self.fc3 = nn.Linear(84, 10)def forward(self, x):# Max pooling over a (2, 2) windowx = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))# If the size is a square, you can specify with a single numberx = F.max_pool2d(F.relu(self.conv2(x)), 2)x = torch.flatten(x, 1) # flatten all dimensions except the batch dimensionx = F.relu(self.fc1(x))x = F.relu(self.fc2(x))x = self.fc3(x)return xnet = Net()
print(net)
contiguous 本身是形容词,表示连续的,关于 contiguous,PyTorch 提供了is_contiguous、contiguous(形容词动用)两个方法 ,分别用于判定Tensor是否是 contiguous 的,以及保证Tensor是contiguous的。
is_contiguous直观的解释是Tensor底层一维数组元素的存储顺序与Tensor按行优先一维展开的元素顺序是否一致
模型训练时,不需要使用forward,只要在实例化一个对象中传入对应的参数就可以自动调用 forward 函数
class Module(nn.Module):def __init__(self):super(Module, self).__init__()# ......def forward(self, x):# ......return x
data = ..... #输入数据
# 实例化一个对象
module = Module()
# 前向传播
module(data)
# 而不是使用下面的
# module.forward(data)
torch.utils.data.DataLoader
数据加载器:结合数据集和采样器,并在数据集上提供单进程或多进程迭代器。
参数:
默认情况下,每个 worker 都将其 PyTorch 种子设置为“base_seed + worker_id”,其中“base_seed”是主进程使用其 RNG 生成的 long。 但是,其他库的种子可能会在初始化工作人员(例如 NumPy)时被复制,导致每个工作人员返回相同的随机数。
(请参阅常见问题解答中的 dataloader-workers-random-seed 部分。)您可以使用torch.initial_seed()访问 :attr:`worker_init_fn 中每个工人的 PyTorch 种子,并使用它 在数据加载之前设置其他种子
nn.modules.module.py
Adds a persistent buffer to the module.向模块添加持久缓冲区。This is typically used to register a buffer that should not to beconsidered a model parameter. For example, BatchNorm's ``running_mean``is not a parameter, but is part of the persistent state.这通常用于注册不应被视为模型参数的缓冲区。例如,BatchNorm的“running_mean”不是参数,而是持久状态的一部分。Buffers can be accessed as attributes using given names.
缓冲区可以使用给定的名称作为属性访问。 Args:name (string): name of the buffer. The buffer can be accessedfrom this module using the given name 名称(字符串):缓冲区的名称。可以使用给定的名称从该模块访问缓冲区tensor (Tensor): buffer to be registered.Example::>>> self.register_buffer('running_mean', torch.zeros(num_features))
pytorch一般情况下,是将网络中的参数保存成orderedDict形式的,这里的参数其实包含两种,一种是模型中各种module含的参数,即nn.Parameter,我们当然可以在网络中定义其他的nn.Parameter参数,另一种就是buffer,前者每次optim.step会得到更新,而不会更新后者。
Tqdm 是一个快速,可扩展的Python进度条,可以在 Python 长循环中添加一个进度提示信息,用户只需要封装任意的迭代器 tqdm(iterator)
https://blog.csdn.net/kongxx/article/details/51553362
https://zhuanlan.zhihu.com/p/86441879?ivk_sa=1024320u
平衡DataParallel带来的显存使用不平衡:官方给的解决方案就是使用 DistributedDataParallel来代替 DataParallel
因为DistributedDataParallel比DataParallel运行的更快, 然后显存分屏的更加均衡. 而且DistributedDataParallel功能更加强悍, 例如分布式的模型(一个模型太大, 以至于无法放到一个GPU上运行, 需要分开到多个GPU上面执行). 只有DistributedDataParallel支持分布式的模型像单机模型那样可以进行多机多卡的运算.
方法设置一个下限min,tensor中有元素小于这个值, 就把对应的值赋为min
torch.split()作用将tensor分成块结构。
torch.split(tensor, ssplit_size_or_section, dim=0)
参数:
tesnor:input,待分输入
split_size_or_sections:需要切分的大小(int or list )
dim:切分维度
output:切分后块结构
当split_size_or_sections为int时,tenor结构和split_size_or_sections,正好匹配,那么ouput就是大小相同的块结构。如果按照split_size_or_sections结构,tensor不够了,那么就把剩下的那部分做一个块处理。
当split_size_or_sections 为list时,那么tensor结构会一共切分成len(list)这么多的小块,每个小块中的大小按照list中的大小决定,其中list中的数字总和应等于该维度的大小,否则会报错(注意这里与split_size_or_sections为int时的情况不同)。
https://zhuanlan.zhihu.com/p/580253389
torch.optim.lr_scheduler 模块提供了一些根据 epoch 训练次数来调整学习率(learning rate)的方法。一般情况下我们会设置随着 epoch 的增大而逐渐减小学习率从而达到更好的训练效果。
CLASStorch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=-1, verbose=False)
参数:
optimizer (Optimizer) – Wrapped optimizer.
step_size (int) – Period of learning rate decay.学习率下降间隔数,若为30,则会在30、60、90…个step时,将学习率调整为lr*gamma。gamma (float) – Multiplicative factor of learning rate decay. Default: 0.1. 学习率调整倍数,默认为0.1倍,即下降10倍。
last_epoch (int) – The index of last epoch. Default: -1.上一个epoch数,这个变量用来指示学习率是否需要调整。当last_epoch符合设定的间隔时,就会对学习率进行调整。当为-1时,学习率设置为初始值。
verbose (bool) – If True, prints a message to stdout for each update. Default: False.
(1)官方推荐方法
#第一种:只存储模型中的参数,该方法速度快,占用空间少(官方推荐使用)
model = VGGNet()
torch.save(model.state_dict(), PATH) #存储model中的参数new_model = VGGNet() #建立新模型
new_model.load_state_dict(torch.load(PATH)) #将model中的参数加载到new_model中#第二种:存储整个模型
model = VGGNet()
# save会把当前目录结构以及py文件class都写入模型中保存下来,于是当把pt文件迁移到其他项目中使用,而其他项目的关于模型相关的目录结构有所变化,就会报no module named models的错误了
torch.save(model, PATH) #存储整个模型new_model = torch.load(PATH) #将整个model加载到new_model中
#new_model 不再需要第一种方法中的建立新模型的步骤'''
关于上面表达式中PATH参数的说明:
PATH参数是你保存文件的路径,并且需要指定保存文件的文件名,如:
torch.save(model, '/home/user/save_model/checkpoint.pth')
即将该模型保存在/home/user/save_model路径下的checkpoint.pth文件中,保存的文件格式约定为.pth或.pt
new_model = torch.load('/home/user/save_model/checkpoint.pth')但是在pytorch1.6版本中,torch.save存储的文件格式采用了新的基于压缩文件的格式 .pth.tar
torch.load依然保留了加载了旧格式.pth的能力
'''
推荐的方式是将·state_dict保存下来,state_dict相当于训练结果中的权重和各种参数,这样在加载时不会受到目录和class名等等的限制
torch.save(my_model.state_dict(), PATH)
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.eval()
(2)保存checkpoint(检查点)
通常在训练模型的过程中,可能会遭遇断电、断网的尴尬,一旦出现这种情况,先前训练的模型就白费了,又得重头开始训练。因此每隔一段时间就将训练模型信息保存一次很有必要。而这些信息不光包含模型的参数信息,还包含其他信息,如当前的迭代次数,优化器的参数等,以便用于后面恢复训练。
state = {'epoch' : epoch + 1, # 保存当前的迭代次数'state_dict' : model.state_dict(), # 保存模型参数'optimizer' : optimizer.state_dict(), # 保存优化器参数..., # 其余一些想保持的参数都可以添加进来...,
}torch.save(state, 'checkpoint.pth.tar') # 将state中的信息保存到checkpoint.pth.tar
# Pytorch 约定使用.tar格式来保存这些检查点# 当想恢复训练时
checkpoint = torch.load('checkpoint.pth.tar')
epoch = checkpoint['epoch']
model.load_state_dict(checkpoint['state_dict']) # 加载模型的参数
optimizer.load_state_dict(checkpoint['optimizer']) # 加载优化器的参数
3)不同设备上的模型存储与加载
#1、在CPU上存储模型,在GPU上加载模型
#CPU存储
torch.save(model.state_dict(), PATH)
#GPU加载
device = torch.device('cuda')
model = Model()
model.load_state_dict(torch.load(PATH, map_location='cuda:0')) #可以选择任意GPU设备
model.to(device)----------------------------------------------------------------------------------
#2、在GPU上存储,CPU上加载
#GPU存储
torch.save(model.state_dict(), PATH)
#CPU加载
device = torch.device('cpu')
model = Model()
model.load_state_dict(torch.load(PATH, map_location=device))----------------------------------------------------------------------------------
#3、在GPU上存储,在GPU上加载
#GPU存储
torch.save(model.state_dict(), PATH)
#GPU加载
device = torch.device('cuda')
model = Model()
model.load_state_dict(torch.load(PATH))
model.to(device)----------------------------------------------------------------------------------
#4、存储和加载使用过torch.nn.DataParallel的模型#(1)多卡训练,单卡加载部署'''
这种情况要防止参数保存的时候没有加module,那么保存的参数名称是module.conv1.weight,
而单卡的参数名称是conv1.weight,这时就会报错,找不到相应的字典的错误。
此时可以通过手动的方式删减掉模型中前几位的名称,然后重新加载。
不懂代码可以先看一下第2部分内容模型参数存储内容解析
'''model = torch.nn.DataParallel(model)
#存储
torch.save(model.module.state_dict(), PATH)
#加载
kwargs={'map_location':lambda storage, loc: storage.cuda(gpu_id)}
def load_GPUS(model,model_path,kwargs):state_dict = torch.load(PATH, **kwargs)# create new OrderedDict that does not contain 'module.'from collections import OrderedDictnew_state_dict = OrderedDict()for k, v in state_dict.items():name = k[7:] # remove 'module.'new_state_dict[name] = v# load paramsmodel.load_state_dict(new_state_dict)return model#(2)单卡训练,多卡加载部署'''
此时唯有记住一点,因为单卡训练参数是没有module的,而多卡加载的参数是有module的,
因此需要保证参数加载在模型分发之前。
'''#存储
torch.save(model.state_dict(), PATH)
#加载
model.load_state_dict(torch.load(PATH))
model = torch.nn.DataParallel(model) #模型分发#(3)多卡训练,多卡加载部署'''
环境如果没有变化,则可以直接加载,如果环境有变化,则可以拆解成第1种情况,然后再分发模型。
'''
(4)模型变更后的参数加载操作
当我们使用像resnet50、resnet101这样的网络时,通常可以从网上对应下载到这些模型的预训练参数文件,但是我们所使用的模型,可能需要在resnet50或resnet101网络上进行一些修改,比如增加一些结构,或者删除一些结构。所以我们只希望加载修改后的模型与原来的模型之间具有相同结构部分的参数。
#假设下载到的原有模型参数文件为checkpoint.pth.tar
model = OurModel()
model_checkpoint = torch.load('checkpoint.pth.tar')
pretrain_model_dict = model_checkpoint['state_dict']
model_dict = model.state_dict
# 注意torch.jit.save保存的时torchscript模型,所以要先进行转换
scripted_module = torch.jit.script(MyModule())
torch.jit.save(scripted_module, 'mymodule.pt')
torch.jit.load('mymodule.pt')
torch.nn.Module模块中的state_dict变量存放训练过程中需要学习的权重和偏执系数,state_dict作为python的字典对象将每一层的参数映射成tensor张量,需要注意的是torch.nn.Module模块中的state_dict只包含卷积层和全连接层的参数,当网络中存在batchnorm时,例如vgg网络结构,torch.nn.Module模块中的state_dict也会存放batchnorm’s running_mean。