目录
1 PyTorch的各个组件
1.1 基本配置
1.1.1 导入包
import os
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import torch.optim as optimizer
1.1.2 超参数
- batch size:批次的大小,决定一次读入多少数据
- learning rate:初始学习率(初始)
- max_epoches:最大迭代次数
- num_workers:决定使用多少线程读入数据
1.1.3 GPU配置
有以下几种方式:
# 方案一:使用os.environ,这种情况如果使用GPU不需要设置
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
#方案三:对于想用gpu的macOS用户,需要搭配预览版的pytorch(Nightly)
device = torch.device("mps")
1.2 数据读入
Dataset + Dataloader
1.2.1 Dataset
常见有两种方式。
1.2.1.1 使用pytorch自带的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)
1.2.1.2 自己定义dataset
我们可以定义自己的Dataset类来实现灵活的数据读取,定义的类需要继承PyTorch自身的Dataset类。主要包含三个函数:
- __ init __: 用于向类中传入外部参数,同时定义样本集
- __ getitem __: 用于逐个读取样本集合中的元素,可以进行一定的变换,并将返回训练/验证所需的数据
- __ len __: 用于返回数据集的样本数
1.2.2 Dataloader
from torch.utils.data import DataLoader
它的参数:
- batch_size:样本是按“批”读入的,batch_size就是每次读入的样本数
- num_workers:有多少个进程用于读取数据
- shuffle:是否将读入的数据打乱
- drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练
1.3 模型构建
Module 类是 nn 模块里提供的一个模型构造类,是所有神经⽹网络模块的基类,我们可以继承它来定义我们想要的模型。下面继承 Module 类构造多层感知机。这里定义的 MLP 类重载了 Module 类的 init 函数和 forward 函数。它们分别用于创建模型参数和定义前向计算。前向计算也即正向传播。
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)
1.4 模型初始化
torch.nn.init提供了常用的初始化方法,如:
- torch.nn.init.uniform_(tensor, a=0.0, b=1.0)
- torch.nn.init.normal_(tensor, mean=0.0, std=1.0)
- torch.nn.init.constant_(tensor, val)
- torch.nn.init.ones_(tensor)
- torch.nn.init.zeros_(tensor)
- torch.nn.init.eye_(tensor)
通常使用isinstance()来进行判断模块属于什么类型,对于不同的类型层,我们就可以设置不同的权值初始化的方法。
常常将各种初始化方法定义为一个initialize_weights()的函数并在模型初始后进行使用。
1.5 损失函数:
- 二分类交叉熵损失函数
- torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction=‘mean’)
- 功能:计算二分类任务时的交叉熵(Cross Entropy)函数。
- 交叉熵损失函数
- torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction=‘mean’)
- 功能:计算交叉熵(Cross Entropy)函数。
- L1损失函数
- torch.nn.L1Loss(size_average=None, reduce=None, reduction=‘mean’)
- 功能: 计算输出 y 和真实标签 target 之间的差值的绝对值。
- MSE损失函数
- torch.nn.MSELoss(size_average=None, reduce=None, reduction=‘mean’)
- 功能:计算输出 y 和真实标签 target 之差的平方。
- 平滑L1 (Smooth L1)损失函数
- torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction=‘mean’, beta=1.0)
- 功能:L1的平滑输出,其功能是减轻离群点带来的影响。
- 目标泊松分布的负对数似然损失
- torch.nn.PoissonNLLLoss(log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction=‘mean’)
- 功能:泊松分布的负对数似然损失函数。
- KL散度
- torch.nn.KLDivLoss(size_average=None, reduce=None, reduction=‘mean’, log_target=False)
- 功能:计算KL散度,也就是计算相对熵。用于连续分布的距离度量,并且对离散采用的连续输出空间分布进行回归通常很有用。
- MarginRankingLoss
- torch.nn.MarginRankingLoss(margin=0.0, size_average=None, reduce=None, reduction=‘mean’)
- 功能:计算两个向量之间的相似度,用于排序任务。该方法用于计算两组数据之间的差异。
- 多标签边界损失函数
- torch.nn.MultiLabelMarginLoss(size_average=None, reduce=None, reduction=‘mean’)
- 功能:对于多标签分类问题计算损失函数。
- 二分类损失函数
- torch.nn.SoftMarginLoss(size_average=None, reduce=None, reduction=‘mean’)torch.nn.(size_average=None, reduce=None, reduction=‘mean’)
- 功能: 计算二分类的 logistic 损失。
- 多分类的折页损失
- torch.nn.MultiMarginLoss(p=1, margin=1.0, weight=None, size_average=None, reduce=None, reduction=‘mean’)
- 功能:计算多分类的折页损失。
- 三元组损失
- torch.nn.TripletMarginLoss(margin=1.0, p=2.0, eps=1e-06, swap=False, size_average=None, reduce=None, reduction=‘mean’)
- 功能: 计算三元组损失。
- HingEmbeddingLoss
- torch.nn.HingeEmbeddingLoss(margin=1.0, size_average=None, reduce=None, reduction=‘mean’)
- 功能:对输出的embedding结果做Hing损失计算。
- 余弦相似度
- torch.nn.CosineEmbeddingLoss(margin=0.0, size_average=None, reduce=None, reduction=‘mean’)
- 功能:对两个向量做余弦相似度。
- CTC损失函数
- torch.nn.CTCLoss(blank=0, reduction=‘mean’, zero_infinity=False)
- 功能:用于解决时序类数据的分类。
1.6 训练和评估
首先设置模型的状态:
- 如果是训练状态,那么模型的参数应支持反向传播的修改;
- 如果是验证/测试状态,则不应该修改模型参数。
在PyTorch中,模型的状态设置非常简便,如下的两个操作二选一即可:
model.train() # 训练状态
model.eval() # 验证/测试状态
训练过程:
用for循环读取DataLoader中的全部数据:
for data, label in train_loader:
之后将数据放到GPU上用于后续计算,此处以.cuda()为例
data, label = data.cuda(), label.cuda()
开始用当前批次数据做训练时,应当先将优化器的梯度置零:
optimizer.zero_grad()
之后将data送入模型中训练:
output = model(data)
根据预先定义的criterion计算损失函数:
loss = criterion(output, label)
将loss反向传播回网络:
loss.backward()
使用优化器更新模型参数:
optimizer.step()
验证/测试的流程基本与训练过程一致,不同点在于:
- 需要预先设置torch.no_grad,以及将model调至eval模式
- 不需要将优化器的梯度置零
- 不需要将loss反向回传到网络
- 不需要更新optimizer
1.7 可视化
- 分类的ROC曲线
- 卷积网络中的卷积核
- 训练/验证过程的损失函数曲线
1.8 优化器
优化器是根据网络反向传播的梯度信息来更新网络的参数,以起到降低loss函数计算值,使得模型输出更加接近真实标签。
Pytorch优化器库torch.optim
,提供了十种优化器:
- torch.optim.ASGD
- torch.optim.Adadelta
- torch.optim.Adagrad
- torch.optim.Adam
- torch.optim.AdamW
- torch.optim.Adamax
- torch.optim.LBFGS
- torch.optim.RMSprop
- torch.optim.Rprop
- torch.optim.SGD
- torch.optim.SparseAdam
以上这些优化算法均继承于Optimizer
2 FashionMNIST时装分类实战
通过FashionMNIST时装分类来学习PyTorch进行深度学习的流程。FashionMNIST数据集中包含已经预先划分好的训练集和测试集,其中训练集共60,000张图像,测试集共10,000张图像。每张图像均为单通道黑白图像,大小为32*32pixel,分属10个类别。
2.1 基本配置
- 导入必要的packages
- os, numpy, pandas, torch, torch.nn, torch.optim, torch.utils.data
- 配置训练的超参数
- batch size, learning rate, max_epoches, num_workers
- 配置训练使用的硬件设备
- CPU/GPU, which GPUs
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
# 配置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")
device = torch.device('mps') #这里使用的是M1 Pro的GPU
## 配置其他超参数,如batch_size, num_workers, learning rate, 以及总的epochs
batch_size = 256
num_workers = 4 # 对于Windows用户,这里应设置为0,否则会出现多线程错误
lr = 1e-4
epochs = 20
2.2 数据读入
数据读入和加载,这里同时展示两种方式:
- 下载并使用PyTorch提供的内置数据集:只适用于常见的数据集,如MNIST,CIFAR10等,PyTorch官方提供了数据下载。这种方式往往适用于快速测试方法(比如测试下某个idea在MNIST数据集上是否有效)
- 从网站下载以csv格式存储的数据,读入并转成预期的格式:需要自己构建Dataset,这对于PyTorch应用于自己的工作中十分重要。
- 如何读取格式不定的本地数据:定义一个Dataset
-
- __ init __, __ getitem __, __ len __
- 将数据加载以供模型输入;定义Dataloader
-
- batch_size, num_workers, shuffle, drop_last, pin_memory
同时,还需要对数据进行必要的变换,比如说需要将图片统一为一致的大小,以便后续能够输入网络训练;需要将数据格式转为Tensor类,等等。
- batch_size, num_workers, shuffle, drop_last, pin_memory
这些变换可以很方便地借助torchvision
包来完成,这是PyTorch官方用于图像处理的工具库,上面提到的使用内置数据集的方式也要用到。PyTorch的一大方便之处就在于它是一整套“生态”,有着官方和第三方各个领域的支持。这些内容我们会在后续课程中详细介绍。
# 首先设置数据变换
from torchvision import transforms
image_size = 28 #可以随便设置,但是要与后面的模型一致
data_transform = transforms.Compose([
transforms.ToPILImage(), # 这一步取决于后续的数据读取方式,如果使用内置数据集则不需要
transforms.Resize(image_size), #改变大小
transforms.ToTensor() #转成tensor
])
## 读取方式一:使用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)
## 读取方式二:读入csv格式的数据,自行构建Dataset类
# csv数据下载链接:https://www.kaggle.com/zalando-research/fashionmnist
class FMDataset(Dataset):
def __init__(self, df, transform=None):
self.df = df
self.transform = transform
self.images = df.iloc[:,1:].values.astype(np.uint8) #提取图像
self.labels = df.iloc[:, 0].values #提取标签
def __len__(self):
return len(self.images) #图片数量
def __getitem__(self, idx): #决定dataset是如何构建的,通过idx索引图片
image = self.images[idx].reshape(28,28,1) #变成28*28的图像,1用于加一个通道
label = int(self.labels[idx])
if self.transform is not None:
image = self.transform(image)
else:
image = torch.tensor(image/255., dtype=torch.float)#变成0-1的tensor
label = torch.tensor(label, dtype=torch.long)
return image, label
train_df = pd.read_csv("./fashion-mnist_train.csv")
test_df = pd.read_csv("./fashion-mnist_test.csv")
train_data = FMDataset(train_df, data_transform)
test_data = FMDataset(test_df, data_transform)
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)
- batch_size:一次读入模型的图片数量
- shuffle:打乱后采样
- num_workers:用多少个线程读,数值越大,读得越快
- drop_last:最后一个batch的图片数量可能小于batch_size,如果drop就会将其丢弃
- pin_memory:空间换时间,将数据存起来,增加内存但运行更快,默认False
读入后,我们可以做一些数据可视化操作,主要是验证我们读入的数据是否正确:
import matplotlib.pyplot as plt
image, label = next(iter(train_loader))
print(image.shape, label.shape)
plt.imshow(image[0][0], cmap="gray")
运行到这里,我的jupyter notebook就开始内核崩溃挂掉,上网查了很多资料,其实主要还是依赖的冲突问题,解决方案查看这里。
next(iterable[, default])
- 函数要和生成迭代器的 iter() 函数一起使用。
- iterable – 可迭代对象
- default – 可选,用于设置在没有下一个元素时返回该默认值,如果不设置,又没有下一个元素则会触发 StopIteration 异常。
2.3 模型构建
- 神经网络的构造:基于nn.Module
- __ init __, forward
- 神经网络通过“层定义+层顺序”的方式构建
- 常见层:nn.Conv2, nn.MaxPool2d, nn.Linear, nn.ReLU…
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) # 10的设定比较关键,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.to(device)
# model = nn.DataParallel(model).cuda() # 多卡训练时的写法,之后的课程中会进一步讲解
2.4 损失函数
- torch.nn提供了多种预定义的损失函数
- 可以自己定义损失函数
- 根据实际需求选用对应的损失函数
- 损失函数常用操作
- backward()
cirterion = nn.CrossEntropyLoss()
2.5 优化器
- torch.optim提供了多种预定义的优化器
- 可以自己定义优化器
- 根据实际需求选用对应的损失函数
- 优化器常用操作:
- step(), zero_grad(), load_state_dict()…
optimizer = optim.Adam(model.parameters(),lr=0.001)
1.6训练与评估
- 模型状态设置
- model.train(). model.eval()
- 训练流程:读取、转换、梯度清零、输入、计算损失、反向传播、参数更新
- 验证流程、读取、转换、输入、计算损失、计算指标
def train(epoch):
model.train()
train_loss = 0
for data, label in train_loader:
data, label = data.to(device), label.to(device) # 把data和label放在gpu上
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))
如果需要对每一个batch进行一些操作,可以使用 for i,(data,label) in enumerate(train_loader)
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.to(device), label.to(device)
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))
模型至此搭建好,接下来进行训练!
for epoch in range(1, epochs+1):
train(epoch)
val(epoch)