流程与机器学习类似,但有以下区别:
深度学习中训练和验证过程最大的特点在于读入数据是按批的,每次读入一个批次的数据放入GPU中训练,然后将损失函数反向传播回网络最前面的层,同时使用优化器调整网络参数。这里会涉及到各个模块配合的问题。训练/验证后还需要根据设定好的指标计算模型表现。[1]
在使用PyTorch的过程中需要导入一些python的包和调用一些PyTorch自身的模块来帮助我们实现功能。
首先导入一些必须的包:
# python
import os
import numpy as np # 一个开源的Python库,主要用在数据分析和科学计算
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import torch.optim as optimizer
注:
然后可以预先设置好一些超参数:
# python
batch_size = 16 # 每次投入训练的数据条数
lr = 1e-4 # 优化器的初始学习率
max_epochs = 100 # 训练回合数
GPU两种常见的设置方式:
(1)使用os.environ,这种情况如果使用GPU后面不需要再设置
# python
os.environ['CUDA_VISIBLE_DEVICES'] = '0, 1'
(2)使用“device”,后续对要使用GPU的变量用 .to(device) 即可
# python
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
根据不同的需求,可能还会有一些其他模块和参数需要设置。
PyTorch中数据读入是通过torch.utils.data模块中的 Dataset + DataLoader类的方式完成的,流程如下:
注:这一过程通常可以把一张生图通过标准化、resize等操作转变成我们需要的 [B, C, H, W] 形状的 Tensor。[4]
torchvision模块一般会随着pytorch的安装一起安装到本地,直接import torchvision
就可以使用了。
torchvision包含:
注:torchvision可供使用的数据集有[4]:
# pytorch
['CIFAR10', 'CIFAR100', 'Caltech101', 'Caltech256', 'CelebA']
['Cityscapes', 'CocoCaptions', 'CocoDetection', 'DatasetFolder', 'EMNIST']
['FakeData', 'FashionMNIST', 'Flickr30k', 'Flickr8k', 'HMDB51']
['ImageFolder', 'ImageNet', 'KMNIST', 'Kinetics400', 'LSUN']
['LSUNClass', 'MNIST', 'Omniglot', 'PhotoTour', 'Places365']
['QMNIST', 'SBDataset', 'SBU', 'SEMEION', 'STL10']
['SVHN', 'UCF101', 'USPS', 'VOCDetection', 'VOCSegmentation']
['VisionDataset']
__init__
函数:读取数据文件__getitem__
:支持下标访问__len__
:返回自定义数据集的大小,方便后期遍历注:自定义Dataset类只需要我们做到 1个父类继承,3个函数:
以 cifar10 数据集为例,给出构建Dataset类的方式:
import torch
from torchvision import datasets
train_data = datasets.ImageFolder(train_path, transform=data_transform)
val_data = datasets.ImageFolder(val_path, transform=data_transform)
# data_transform:可以对图像进行一定的变换,如翻转、裁剪等操作,可自己定义
注:
ImageFolder假设所有的文件按文件夹保存,每个文件夹下存储同一个类别的图片,文件夹名为类名,其构造函数如下[5]:
ImageFolder(root, transform=None, target_transform=None, loader=default_loader)
它主要有4个参数:
label是按照文件夹名顺序排序后存成字典,即{类名: 类序号(从0开始)},一般来说最好直接将文件夹命名为从0开始的数字,这样会和ImageFolder实际的label一致。
如下面的例子,图片存放在一个文件夹,另外一个csv文件给出了图片名称对应的标签(label),这种情况下需要自己来定义Dataset类:
class MyDataset(Dataset):
def __init__(self, data_dir, info_csv, image_list, transform=None):
"""
Args:
data_dir: path to image directory.
info_csv: path to the csv file containing image indexes with corresponding labels.
image_list: path to the txt file contains image names to training/validation set
transform: optional transform to be applied on a sample.
"""
label_info = pd.read_csv(info_csv) # 读取label
image_file = open(image_list).readlines() # 读取图片名字
self.data_dir = data_dir # 图片文件夹目录
self.image_file = image_file # 图片名字文件
self.label_info = label_info # 标签信息
self.transform = transform # 图片的转换操作方式
def __getitem__(self, index): # 自定义加载及取出原始数据的形式
"""
Args:
index: the index of item
Returns:
image and its labels
"""
image_name = self.image_file[index].strip('\n')
raw_label = self.label_info.loc[self.label_info['Image_index'] == image_name]
label = raw_label.iloc[:,0]
image_name = os.path.join(self.data_dir, image_name)
image = Image.open(image_name).convert('RGB')
if self.transform is not None: # 自定义对数据预处理的形式
image = self.transform(image)
return image, label
def __len__(self): # 自定义数据集的大小
return len(self.image_file)
然后就可以使用DataLoader读取数据了:
from torch.utils.data import DataLoader
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, num_workers=4, shuffle=True, drop_last=True)
val_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, num_workers=4, shuffle=False)
DataLoader类为Dataset类对象提供了[6]:
DataLoader类的构造函数如下[7]:
DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
batch_sampler=None, num_workers=0, collate_fn=None,
pin_memory=False, drop_last=False, timeout=0,
worker_init_fn=None)
DataLoader的主要参数如下[7]:
Dataloader 作为迭代器,最基本的使用过程是[7]:
如果想看到上面加载的数据大致情况,可以做图出来:
import matplotlib.pyplot as plt
images, labels = next(iter(val_loader))
print(images.shape)
plt.imshow(images[0].transpose(1,2,0))
plt.show() # 在窗口显示图像
注:
matplotlib.pyplot.imshow(X, cmap=None)
# X:要绘制的图像或数组
# cmap: 颜色图谱(colormap), 默认绘制为RGB(A)颜色空间
PyTorch中神经网络构造一般是基于 Module 类的模型来完成的,它让模型构造更加灵活。
Module 类是 nn 模块里提供的一个模型构造类,是所有神经⽹网络模块的基类。在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__(用于创建模型参数)和构造函数forward(定义前向计算)这两个方法。但有一些注意的点 [11]:
如继承Module类构造多层感知机:
import torch
from torch import nn
class MLP(nn.Module):
# 声明带有模型参数的层,这里声明了两个全连接层
def __init__(self, **kwargs):
# 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Linear(784, 256)
self.act = nn.ReLU()
self.output = nn.Linear(256,10)
# 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
def forward(self, x):
o = self.act(self.hidden(x))
return self.output(o)
以上的 MLP 类中⽆须定义反向传播函数。系统将通过⾃动求梯度⽽自动⽣成反向传播所需的 backward 函数。
可以实例化 MLP 类得到模型变量 net 。下⾯的代码初始化 net 并传入输⼊数据 X 做一次前向计算。其中, net(X) 会调用 MLP 类继承自 Module 类的 call 函数,这个函数将调⽤ MLP 类定义的forward 函数来完成前向计算:
X = torch.rand(2,784)
net = MLP()
print(net)
net(X)
用前一节建的 pytorchenv 虚拟环境,在Jupyter Notebook运行结果如下:
注:pytorch中其实一般没有特别明显的Layer和Module的区别,不管是自定义层、自定义块、自定义模型,都是通过继承Module类完成的。
深度学习的一个魅力在于神经网络中各式各样的层,例如全连接层、卷积层、池化层与循环层等等。虽然PyTorch提供了⼤量常用的层,但有时候我们依然希望⾃定义层。这里我们会介绍如何使用 Module 来自定义层,从而可以被反复调用。
下⾯构造的 MyLayer 类通过继承 Module 类自定义了一个将输入减掉均值后输出的层,并将层的计算定义在了 forward 函数里。这个层里不含模型参数。
import torch
from torch import nn
class MyLayer(nn.Module):
def __init__(self, **kwargs):
super(MyLayer, self).__init__(**kwargs)
def forward(self, x):
return x - x.mean()
测试,实例化该层,然后做前向计算:
layer = MyLayer()
layer(torch.tensor([1, 2, 3, 4, 5], dtype=torch.float))
还可以自定义含模型参数的自定义层,模型参数可以通过训练学出。
Parameter 类其实是 Tensor 的子类,Parameter作为Module类的参数,可以自动的添加到Module类的参数列表中,并且可以使用Module.parameters()提供的迭代器获取到[12]。如果一 个 Tensor 是 Parameter,那么它会⾃动被添加到模型的参数列表里,我们应该将参数定义成 Parameter。定义的方法:
如下:
class MyListDense(nn.Module):
def __init__(self):
super(MyListDense, self).__init__()
self.params = nn.ParameterList([nn.Parameter(torch.randn(4, 4)) for i in range(3)])
self.params.append(nn.Parameter(torch.randn(4, 1)))
def forward(self, x):
for i in range(len(self.params)):
x = torch.mm(x, self.params[i])
return x
net = MyListDense()
print(net)
class MyDictDense(nn.Module):
def __init__(self):
super(MyDictDense, self).__init__()
self.params = nn.ParameterDict({
'linear1': nn.Parameter(torch.randn(4, 4)),
'linear2': nn.Parameter(torch.randn(4, 1))
})
self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))}) # 新增
def forward(self, x, choice='linear1'):
return torch.mm(x, self.params[choice])
net = MyDictDense()
print(net)
1)二维卷积层
如下:
import torch
from torch import nn
# 卷积运算(二维互相关)
def corr2d(X, K):
h, w = K.shape
X, K = X.float(), K.float()
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
return Y
# 二维卷积层
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super(Conv2D, self).__init__()
self.weight = nn.Parameter(torch.randn(kernel_size))
self.bias = nn.Parameter(torch.randn(1))
def forward(self, x):
return corr2d(x, self.weight) + self.bias
注:
填充(padding)是指在输⼊高和宽的两侧填充元素(通常是0元素)。
例如创建一个⾼和宽为3的二维卷积层,然后设输⼊高和宽两侧的填充数分别为1。给定一 个高和宽为8的输入,我们发现输出的高和宽也是8:
import torch
from torch import nn
import torch
from torch import nn
# 定义一个函数来计算卷积层。它对输入和输出做相应的升维和降维
def comp_conv2d(conv2d, X):
# (1, 1)代表批量大小和通道数
X = X.view((1, 1) + X.shape)
Y = conv2d(X)
return Y.view(Y.shape[2:]) # 排除不关心的前两维:批量和通道
# 注意这里是两侧分别填充1⾏或列,所以在两侧一共填充2⾏或列
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3,padding=1)
X = torch.rand(8, 8)
comp_conv2d(conv2d, X).shape
结果是:
当卷积核的高和宽不同时,我们也可以通过设置高和宽上不同的填充数使输出和输入具有相同的高和宽:
使用高为5、宽为3的卷积核。在⾼和宽两侧的填充数分别为2和1
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
结果:
在二维互相关运算中,卷积窗口从输入数组的最左上方开始,按从左往右、从上往下 的顺序,依次在输⼊数组上滑动。我们将每次滑动的行数和列数称为步幅(stride):
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
comp_conv2d(conv2d, X).shape
2)池化层
把池化层的前向计算实现在pool2d函数里:
import torch
from torch import nn
def pool2d(X, pool_size, mode='max'):
p_h, p_w = pool_size
Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode == 'max':
Y[i, j] = X[i: i + p_h, j: j + p_w].max()
elif mode == 'avg':
Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
return Y
实例化并计算:
X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=torch.float)
pool2d(X, (2, 2)) # 默认最大池化
pool2d(X, (2, 2), 'avg') # 平均池化
结果:
总结:可以使用torch.nn包来构建神经网络,nn包依赖于autograd包来定义模型并对它们求导;一个nn.Module包含各个层和一个forward(input)方法,该方法返回output。
3)模型示例
通常会根据实际模型使用torch.nn.init进行初始化,通常使用isinstance判断模块属于什么类型。
import torch
import torch.nn as nn
conv = nn.Conv2d(1,3,3)
linear = nn.Linear(10,1)
print(isinstance(conv,nn.Conv2d)) # isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()
print(isinstance(linear,nn.Conv2d))
对于不同的类型层,可以设置不同的权值初始化的方法:
# 查看随机初始化的conv参数
print(conv.weight.data)
# 查看linear的参数
print(linear.weight.data)
# 对conv进行kaiming初始化
torch.nn.init.kaiming_normal_(conv.weight.data)
print(conv.weight.data)
# 对linear进行常数初始化
torch.nn.init.constant_(linear.weight.data,0.3)
print(linear.weight.data)
常常将各种初始化方法定义为一个initialize_weights()的函数并在模型初始后进行使用:
def initialize_weights(self):
for m in self.modules():
# 判断是否属于Conv2d
if isinstance(m, nn.Conv2d):
torch.nn.init.xavier_normal_(m.weight.data)
# 判断是否有偏置
if m.bias is not None:
torch.nn.init.constant_(m.bias.data,0.3)
elif isinstance(m, nn.Linear):
torch.nn.init.normal_(m.weight.data, 0.1)
if m.bias is not None:
torch.nn.init.zeros_(m.bias.data)
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zeros_()
函数流程是遍历当前模型的每一层,然后判断各层属于什么类型,然后根据不同类型层,设定不同的权值初始化方法。例如:
# 模型的定义
class MLP(nn.Module):
# 声明带有模型参数的层,这里声明了两个全连接层
def __init__(self, **kwargs):
# 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Conv2d(1,1,3)
self.act = nn.ReLU()
self.output = nn.Linear(10,1)
# 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
def forward(self, x):
o = self.act(self.hidden(x))
return self.output(o)
mlp = MLP()
print(list(mlp.parameters()))
print("-------初始化-------")
initialize_weights(mlp)
print(list(mlp.parameters()))
torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')
m = nn.Sigmoid()
loss = nn.BCELoss()
input = torch.randn(3, requires_grad=True)
target = torch.empty(3).random_(2)
output = loss(m(input), target)
output.backward()
print('BCELoss损失函数的计算结果为',output)
torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
loss = nn.CrossEntropyLoss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.empty(3, dtype=torch.long).random_(5)
output = loss(input, target)
output.backward()
print(output)
L1 Loss也称为平均绝对值误差(MAE),是指模型预测值f(x)和真实值y之间绝对差值的平均值。
torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
loss = nn.L1Loss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss(input, target)
output.backward()
print('L1损失函数的计算结果为',output)
L2 Loss也称为均方误差(MSE),是指模型预测值f(x)和真实值y之间差值平方的平均值。
torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
loss = nn.MSELoss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss(input, target)
output.backward()
print('MSE损失函数的计算结果为',output)
Smooth L1就是一个平滑版的L1 Loss,是一个分段函数,在[-1,1]之间就是L2损失,解决L1在0处有折点,在[-1, 1]区间以外就是L1损失,解决离群点梯度爆炸问题[15]。
torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction='mean', beta=1.0)
loss = nn.SmoothL1Loss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss(input, target)
output.backward()
print('SmoothL1Loss损失函数的计算结果为',output)
inputs = torch.linspace(-10, 10, steps=5000)
target = torch.zeros_like(inputs)
loss_f_smooth = nn.SmoothL1Loss(reduction='none')
loss_smooth = loss_f_smooth(inputs, target)
loss_f_l1 = nn.L1Loss(reduction='none')
loss_l1 = loss_f_l1(inputs,target)
plt.plot(inputs.numpy(), loss_smooth.numpy(), label='Smooth L1 Loss')
plt.plot(inputs.numpy(), loss_l1, label='L1 loss')
plt.xlabel('x_i - y_i')
plt.ylabel('loss value')
plt.legend()
plt.grid()
plt.show()
可以看出对于smoothL1来说,在0这个尖端处过渡更为平滑。
真实标签服从泊松分布的负对数似然损失,神经网络的输出作为泊松分布的参数 λ \lambda λ。
torch.nn.PoissonNLLLoss(log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction='mean')
loss = nn.PoissonNLLLoss()
log_input = torch.randn(5, 2, requires_grad=True)
target = torch.randn(5, 2)
output = loss(log_input, target)
output.backward()
print('PoissonNLLLoss损失函数的计算结果为',output)
考虑某个未知的分布 p(x),假定用一个近似的分布 q(x) 对它进行建模。如果我们使用 q(x) 来建立一个编码体系,用来把 x 的值传给接收者,那么由于我们使用了q(x)而不是真实分布p(x),平均编码长度比用真实分布p(x)进行编码增加的信息量(单位是 nat )为[17]:
这被称为分布p(x)和分布q(x)之间的相对熵(relative entropy)或者KL散 度( Kullback-Leibler divergence )。
torch.nn.KLDivLoss(size_average=None, reduce=None, reduction='mean', log_target=False)
实例:
inputs = torch.tensor([[0.5, 0.3, 0.2], [0.2, 0.3, 0.5]])
target = torch.tensor([[0.9, 0.05, 0.05], [0.1, 0.7, 0.2]], dtype=torch.float)
loss = nn.KLDivLoss()
output = loss(inputs,target)
print('KLDivLoss损失函数的计算结果为',output)
排序损失函数,对于包含 N N N个样本的batch数据 D ( x 1 , x 2 , y ) , x 1 , x 2 D(x_1,x_2,y), x_1, x_2 D(x1,x2,y),x1,x2 是给定的待排序的两个输入, y y y代表真实的标签,属于{1,-1}。当 y = 1 y=1 y=1时, x 1 x_1 x1应该排在 x 2 x_2 x2之前,当 y = − 1 y=-1 y=−1时, x 1 x_1 x1应该排在 x 2 x_2 x2之后。第 n n n个样本对应的loss计算如下[18]:
若 x 1 , x 2 x_1, x_2 x1,x2排序正确且 − y ∗ ( x 1 − x 2 ) > margin -y *(x 1-x 2)>\operatorname{margin} −y∗(x1−x2)>margin, 则loss为0;其他情况下是loss为 − y ∗ ( x 1 − x 2 ) + margin -y *(x_1-x_2)+\operatorname{margin} −y∗(x1−x2)+margin。
torch.nn.MarginRankingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')
注:reduction有三种取值: m e a n , s u m , n o n e mean, sum, none mean,sum,none,对应不同的返回 ℓ ( x , y ) \ell(x, y) ℓ(x,y)。 默认为 m e a n mean mean,对应于上述 l o s s loss loss的计算( m a r g i n margin margin默认取值0)[18]:
实例:
loss = nn.MarginRankingLoss()
input1 = torch.randn(3, requires_grad=True)
input2 = torch.randn(3, requires_grad=True)
target = torch.randn(3).sign()
output = loss(input1, input2, target)
output.backward()
print('MarginRankingLoss损失函数的计算结果为',output)
torch.nn.MultiLabelMarginLoss(size_average=None, reduce=None, reduction='mean')
loss = nn.MultiLabelMarginLoss()
x = torch.FloatTensor([[0.9, 0.2, 0.4, 0.8]])
# for target y, only consider labels 3 and 0, not after label -1
y = torch.LongTensor([[3, 0, -1, 1]])# 真实的分类是,第3类和第0类
output = loss(x, y)
print('MultiLabelMarginLoss损失函数的计算结果为',output)
torch.nn.SoftMarginLoss(size_average=None, reduce=None, reduction='mean')torch.nn.(size_average=None, reduce=None, reduction='mean')
公式[20]:
y y y表示的是真实的标签, y ^ \hat{y} y^表示模型的输出标签。
实例:
inputs = torch.tensor([[0.3, 0.7], [0.5, 0.5]]) # 两个样本,两个神经元
target = torch.tensor([[-1, 1], [1, -1]], dtype=torch.float) # 该 loss 为逐个神经元计算,需要为每个神经元单独设置标签
loss_f = nn.SoftMarginLoss()
output = loss_f(inputs, target)
print('SoftMarginLoss损失函数的计算结果为',output)
多分类合页损失函数(hinge loss),对于一个样本不是考虑样本输出与真实类别之间的误差,而是考虑对应真实类别与其他类别之间的误差[21]。
对于包含 N N N个样本的batch数据 D ( x , y ) D ( x , y ) D(x,y) , x x x为神经网络的输出, y y y 是真实的类别标签,假设类别数为 C C C, 0 ≤ y n ≤ C − 1 0 ≤ y_n ≤ C − 1 0≤yn≤C−1,第 n n n个样本的损失值 l n l_n ln计算如下[21]:
为了处理多个类别之间的样本不平衡问题,对于每一类可传入相应的权值 w w w[21]:
torch.nn.MultiMarginLoss(p=1, margin=1.0, weight=None, size_average=None, reduce=None, reduction='mean')
注:reduction有三种取值 m e a n , s u m , n o n e mean, sum, none mean,sum,none,对应不同的返回 ℓ ( x , y ) \ell(x, y) ℓ(x,y) ,默认为 m e a n mean mean,对应于一般情况下整体 l o s s loss loss的计算[21]:
实例:
inputs = torch.tensor([[0.3, 0.7], [0.5, 0.5]])
target = torch.tensor([0, 1], dtype=torch.long)
loss_f = nn.MultiMarginLoss()
output = loss_f(inputs, target)
print('MultiMarginLoss损失函数的计算结果为',output)
torch.nn.TripletMarginLoss(margin=1.0, p=2.0, eps=1e-06, swap=False, size_average=None, reduce=None, reduction='mean')
定义三种图像分别为Anchor(A)、Positive§、Negative(N)
定义一个三元组的损失函数为[22]:
实例:
triplet_loss = nn.TripletMarginLoss(margin=1.0, p=2)
anchor = torch.randn(100, 128, requires_grad=True)
positive = torch.randn(100, 128, requires_grad=True)
negative = torch.randn(100, 128, requires_grad=True)
output = triplet_loss(anchor, positive, negative)
output.backward()
print('TripletMarginLoss损失函数的计算结果为',output)
torch.nn.HingeEmbeddingLoss(margin=1.0, size_average=None, reduce=None, reduction='mean')
公式[23]:
注:它输入 x x x和 y y y(1或者-1), m a r g i n margin margin默认为1。
什么时候用[23]:
实例:
loss_f = nn.HingeEmbeddingLoss()
inputs = torch.tensor([[1., 0.8, 0.5]])
target = torch.tensor([[1, 1, -1]])
output = loss_f(inputs,target)
print('HingEmbeddingLoss损失函数的计算结果为',output)
torch.nn.CosineEmbeddingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')
实例:
loss_f = nn.CosineEmbeddingLoss()
inputs_1 = torch.tensor([[0.3, 0.5, 0.7], [0.3, 0.5, 0.7]])
inputs_2 = torch.tensor([[0.1, 0.3, 0.5], [0.1, 0.3, 0.5]])
target = torch.tensor([1, -1], dtype=torch.float)
output = loss_f(inputs_1,inputs_2,target)
print('CosineEmbeddingLoss损失函数的计算结果为',output)
torch.nn.CTCLoss(blank=0, reduction='mean', zero_infinity=False)
什么时候用:序列标注
实例:
# Target are to be padded
T = 50 # Input sequence length
C = 20 # Number of classes (including blank)
N = 16 # Batch size
S = 30 # Target sequence length of longest target in batch (padding length)
S_min = 10 # Minimum target length, for demonstration purposes
# Initialize random batch of input vectors, for *size = (T,N,C)
input = torch.randn(T, N, C).log_softmax(2).detach().requires_grad_()
# Initialize random batch of targets (0 = blank, 1:C = classes)
target = torch.randint(low=1, high=C, size=(N, S), dtype=torch.long)
input_lengths = torch.full(size=(N,), fill_value=T, dtype=torch.long)
target_lengths = torch.randint(low=S_min, high=S, size=(N,), dtype=torch.long)
ctc_loss = nn.CTCLoss()
loss = ctc_loss(input, target, input_lengths, target_lengths)
loss.backward()
# Target are to be un-padded
T = 50 # Input sequence length
C = 20 # Number of classes (including blank)
N = 16 # Batch size
# Initialize random batch of input vectors, for *size = (T,N,C)
input = torch.randn(T, N, C).log_softmax(2).detach().requires_grad_()
input_lengths = torch.full(size=(N,), fill_value=T, dtype=torch.long)
# Initialize random batch of targets (0 = blank, 1:C = classes)
target_lengths = torch.randint(low=1, high=T, size=(N,), dtype=torch.long)
target = torch.randint(low=1, high=C, size=(sum(target_lengths),), dtype=torch.long)
ctc_loss = nn.CTCLoss()
loss = ctc_loss(input, target, input_lengths, target_lengths)
loss.backward()
print('CTCLoss损失函数的计算结果为',loss)
以上步骤完成就可以训练模型了。首先设置模型的状态:
model.train() # 训练状态
model.eval() # 验证/测试状态
然后:
for data, label in train_loader: # 用for循环读取DataLoader中的全部数据
data, label = data.cuda(), label.cuda() # 将数据放到GPU上用于后续计算
optimizer.zero_grad() # 开始当前批次训练之前,先将优化器的梯度置0
output = model(data) # 将data送入模型中训练
loss = criterion(output, label) # 根据预先定义的评判标准计算损失函数
loss.backward() # 进行反向传播
optimizer.step() # 使用优化器更新模型参数
# 这样一个训练过程就完成了
验证/测试的流程基本与训练过程一致,不同点在于:
完整的图像分类的训练过程实例:
def train(epoch):
model.train()
train_loss = 0
for data, label in train_loader:
data, label = data.cuda(), label.cuda()
optimizer.zero_grad()
output = model(data)
loss = criterion(label, output)
loss.backward()
optimizer.step()
train_loss += loss.item()*data.size(0)
train_loss = train_loss/len(train_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
完整的图像分类的测试过程实例:
def val(epoch):
model.eval()
val_loss = 0
with torch.no_grad():
for data, label in val_loader:
data, label = data.cuda(), label.cuda()
output = model(data)
preds = torch.argmax(output, 1)
loss = criterion(output, label)
val_loss += loss.item()*data.size(0)
running_accu += torch.sum(preds == label.data)
val_loss = val_loss/len(val_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, val_loss))
见后续章节
深度学习的本质就是训练一个函数去寻找最优解,通过不断的改变网络参数。如何快速求得最优解,可以使用优化器:
这些优化算法均继承于Optimizer,它是所有优化器的基类:
class Optimizer(object):
def __init__(self, params, defaults):
self.defaults = defaults
self.state = defaultdict(dict)
self.param_groups = []
注:Optimizer有三个属性:
{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}
defaultdict(<class 'dict'>, {tensor([[ 0.3864, -0.0131],
[-0.1911, -0.4511]], requires_grad=True): {'momentum_buffer': tensor([[0.0052, 0.0052],
[0.0052, 0.0052]])}})
[{'params': [tensor([[-0.1022, -1.6890],[-1.5116, -1.7846]], requires_grad=True)], 'lr': 1, 'momentum': 0, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}]
Optimizer还有以下的其他方法:
import os
import torch
# 设置权重,服从正态分布 --> 2 x 2
weight = torch.randn((2, 2), requires_grad=True)
# 设置梯度为全1矩阵 --> 2 x 2
weight.grad = torch.ones((2, 2))
# 输出现有的weight和data
print("The data of weight before step:\n{}".format(weight.data))
print("The grad of weight before step:\n{}".format(weight.grad))
# 实例化优化器
optimizer = torch.optim.SGD([weight], lr=0.1, momentum=0.9)
# 进行一步操作
optimizer.step()
# 查看进行一步后的值,梯度
print("The data of weight after step:\n{}".format(weight.data))
print("The grad of weight after step:\n{}".format(weight.grad))
# 权重清零
optimizer.zero_grad()
# 检验权重是否为0
print("The grad of weight after optimizer.zero_grad():\n{}".format(weight.grad))
# 输出参数
print("optimizer.params_group is \n{}".format(optimizer.param_groups))
# 查看参数位置,optimizer和weight的位置一样,我觉得这里可以参考Python是基于值管理
print("weight in optimizer:{}\nweight in weight:{}\n".format(id(optimizer.param_groups[0]['params'][0]), id(weight)))
# 添加参数:weight2
weight2 = torch.randn((3, 3), requires_grad=True)
optimizer.add_param_group({"params": weight2, 'lr': 0.0001, 'nesterov': True})
# 查看现有的参数
print("optimizer.param_groups is\n{}".format(optimizer.param_groups))
# 查看当前状态信息
opt_state_dict = optimizer.state_dict()
print("state_dict before step:\n", opt_state_dict)
# 进行5次step操作
for _ in range(50):
optimizer.step()
# 输出现有状态信息
print("state_dict after step:\n", optimizer.state_dict())
# 保存参数信息
torch.save(optimizer.state_dict(),os.path.join("/home/cloris/anaconda3/envs/pytorchenv", "optimizer_state_dict.pkl"))
print("----------done-----------")
# 加载参数信息
state_dict = torch.load("/home/cloris/anaconda3/envs/pytorchenv/optimizer_state_dict.pkl") # 需要修改为自己的路径
optimizer.load_state_dict(state_dict)
print("load state_dict successfully\n{}".format(state_dict))
# 输出最后属性信息
print("\n{}".format(optimizer.defaults))
print("\n{}".format(optimizer.state))
print("\n{}".format(optimizer.param_groups))
# 1. 导入必要的包
import os
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
# 2. 配置训练环境和超参数
# 配置GPU,这里有两种方式
# 方案一:使用os.environ
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
# device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
# 配置其他超参数,如batch_size, num_workers, learning rate, 以及总的epochs
batch_size = 256
num_workers = 4 # 对于Windows用户,这里应设置为0,否则会出现多线程错误
lr = 1e-4
epochs = 20
## 读取方式一:使用torchvision自带数据集,下载需要一段时间
from torchvision import datasets
train_data = datasets.FashionMNIST(root='./', train=True, download=True, transform=data_transform)
test_data = datasets.FashionMNIST(root='./', train=False, download=True, transform=data_transform)
# 构建训练和测试数据集完成后,定义DataLoader类,在训练和测试时用于加载数据
train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=num_workers, drop_last=True)
test_loader = DataLoader(test_data, batch_size=batch_size, shuffle=False, num_workers=num_workers)
# 数据可视化验证读取的数据是否正确
import matplotlib.pyplot as plt
image, label = next(iter(train_loader))
print(image.shape, label.shape)
plt.imshow(image[0][0], cmap="gray")
# 手搭CNN,模型构建完成后将其放到GPU上用于训练
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 32, 5),
nn.ReLU(),
nn.MaxPool2d(2, stride=2),
nn.Dropout(0.3),
nn.Conv2d(32, 64, 5),
nn.ReLU(),
nn.MaxPool2d(2, stride=2),
nn.Dropout(0.3)
)
self.fc = nn.Sequential(
nn.Linear(64*4*4, 512),
nn.ReLU(),
nn.Linear(512, 10)
)
def forward(self, x):
x = self.conv(x)
x = x.view(-1, 64*4*4)
x = self.fc(x)
# x = nn.functional.normalize(x)
return x
model = Net()
model = model.cuda()
# model = nn.DataParallel(model).cuda() # 多卡训练时的写法
# 设定损失函数
# 使用torch.nn模块自带的CrossEntropy损失
# PyTorch会自动把整数型的label转为one-hot型,用于计算CE loss
# 这里需要确保label是从0开始的,同时模型不加softmax层(使用logits计算),这也说明了PyTorch训练中各个部分不是独立的,需要通盘考虑
criterion = nn.CrossEntropyLoss()
# criterion = nn.CrossEntropyLoss(weight=[1,1,1,1,3,1,1,1,1,1])
# 设定优化器,这里使用Adam优化器
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练和测试
def train(epoch):
model.train()
train_loss = 0
for data, label in train_loader:
data, label = data.cuda(), label.cuda()
optimizer.zero_grad()
output = model(data)
loss = criterion(output, label)
loss.backward()
optimizer.step()
train_loss += loss.item()*data.size(0)
train_loss = train_loss/len(train_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
def val(epoch):
model.eval()
val_loss = 0
gt_labels = []
pred_labels = []
with torch.no_grad():
for data, label in test_loader:
data, label = data.cuda(), label.cuda()
output = model(data)
preds = torch.argmax(output, 1)
gt_labels.append(label.cpu().data.numpy())
pred_labels.append(preds.cpu().data.numpy())
loss = criterion(output, label)
val_loss += loss.item()*data.size(0)
val_loss = val_loss/len(test_loader.dataset)
gt_labels, pred_labels = np.concatenate(gt_labels), np.concatenate(pred_labels)
acc = np.sum(gt_labels==pred_labels)/len(pred_labels)
print('Epoch: {} \tValidation Loss: {:.6f}, Accuracy: {:6f}'.format(epoch, val_loss, acc)
# 模型保存
# 训练完成后,可以使用torch.save保存模型参数或者整个模型,也可以在训练过程中保存模型
save_path = "./FahionModel.pkl"
torch.save(model, save_path)
引用:
[1] Datawhale项目: 深入浅出PyTorch(强推)
[2] Pytorch各个模块介绍:https://www.cnblogs.com/liulunyang/p/14356789.html
[3] Python 标准库之 os 模块详解:http://www.ityouknow.com/python/2019/10/09/python-os-demonstration-026.html
[4] Pytorch的第一步:(1) Dataset类的使用: https://www.jianshu.com/p/4818a1a4b5bd
[5] pytorch之ImageFolder: https://blog.csdn.net/weixin_40123108/article/details/85099449
[6] Pytorch中数据加载—Dataset类和DataLoader类:https://blog.csdn.net/u012609509/article/details/81264687
[7] PyTorch 之 Dataset 和 Dataloader: https://zhuanlan.zhihu.com/p/339675188
[8] Pytorch——Dataset类和DataLoader类:https://www.cnblogs.com/CircleWang/p/15496422.html
[9] python数字图像处理(5):图像的绘制: https://www.cnblogs.com/denny402/p/5122594.html
[10] Python:next()和iter()的使用说明: https://www.jianshu.com/p/aa6b17d303e9
[11] 使用Module类来自定义模型:https://blog.csdn.net/qq_27825451/article/details/90550890
[12] pytorch源码阅读系列之Parameter类: https://zhuanlan.zhihu.com/p/101052508
[13] PyTorch学习笔记——二分类交叉熵损失函数: https://zhuanlan.zhihu.com/p/59800597
[14] 交叉熵损失函数(Cross Entropy Loss): https://blog.csdn.net/SongGu1996/article/details/99056721
[15] 目标检测回归损失函数——L1、L2、smooth L1:https://zhuanlan.zhihu.com/p/267688490
[16] loss函数之PoissonNLLLoss,GaussianNLLLoss: https://blog.csdn.net/ltochange/article/details/117935410
[17] KL散度理解: https://zhuanlan.zhihu.com/p/39682125
[18] loss函数之MarginRankingLoss: https://www.jianshu.com/p/ea62b3c9cb44
[19] Distribution-Balanced Loss for Multi-Label Classification in Long-Tailed Datasets: https://zhuanlan.zhihu.com/p/420217021
[20] Pytorch中的分类问题损失函数: https://www.jianshu.com/p/70a8b34e0ace
[21] loss函数之MultiMarginLoss, MultiLabelMarginLoss:https://blog.csdn.net/ltochange/article/details/118001115
[22] https://windmissing.github.io/DeepLearningNotes/CV/Face/Triplet.html
[23] PyTorch中的损失函数–MarginRanking/Hinge/Cosine:https://zhuanlan.zhihu.com/p/83364904