(一)PyTorch的主要组成模块
神经网络主要通过对样本的学习训练,不断改变网络的连接权值以及拓扑结构 , 以使网络的输出不断地接近期望的输出 。深度学习模块主要分为以下几个:基本配置;数据读入;模型构建;损失函数;优化器;训练与评估。
1.基本配置
- 先导入你代码中所要用到的库,例如:numpy,torch,os等。
- 配置训练过程用到的超参数
- 数据读入和加载
- 下载并使用PyTorch提供的内置数据集
- 从网站下载以csv格式存储的数据,读入并转换成预期的格式。(这种方式需要自己创建Dataset类)
class MyDataset(Dataset):
def __init__(self):
#用于向类中传入初始参数
def __getitem__(self, index):
#用于逐个读取样本集合中的元素,并对所有样本进行一个统一化的处理,最后返回训练/验证所需的数据
def __len__(self):
#用于返回数据集的样本数量
#在定义完上面的类之后可以将自己下载的csv数据集输入,并进行封装
train_ds=our_dataset(images_train, anno_train)
test_ds=our_dataset(images_test, anno_test)
train_dl=data.DataLoader(train_ds,batch_size=8,shuffle=True) #shuffle表示读入数据是否打乱,一
般只在训练时对数据进行乱序操作。
test_dl=data.DataLoader(test_ds,batch_size=8)
2.模型构建
基于nn.Module类的模型来完成神经网络构建,需要继承Module 类。神经网络是通过“层定义+层顺序”的方式构建起来的。如下构建了两个简单的模型示例:第一个定义了三层全连接层;第二个定义了三层卷积+三层全连接。
class MLP(nn.Module):
def __init__(self, **kwargs):
super(MLP, self).__init__() #继承父类
self.hidden1 = nn.Linear(1024, 512)
self.hidden2 = nn.Linear(512,256)
self.hidden3=nn.Linear(256,10)
self.act = torch.ReLU()
#⽆须定义反向传播函数,系统将通过⾃动求梯度⽽自动⽣成反向传播所需的 backward 函数。
def forward(self, x): # 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
x=self.act(self.hidden1(x))
x=self.act(self.hidden2(x))
return self.hidden3(x)
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1=nn.Conv2d(3,16,3) #in_channels, out_channels, kernel_size,
self.bn1 = nn.BatchNorm2d(16)
self.conv2 = nn.Conv2d(16, 32, 3)
self.bn2 = nn.BatchNorm2d(32)
self.conv3 = nn.Conv2d(32, 64, 3)
self.bn3=nn.BatchNorm2d(64)
self.pool=nn.MaxPool2d(2,2) #每一层卷积后使用池化层来减小输入的高和宽
self.drop=nn.Dropout(0.5) #用于抑制过拟合的,全连接层使用
self.drop2d=nn.Dropout2d(0.5)#四维图片数据使用
self.fc1=nn.Linear(64*10*10,1024)
self.bn_f1=nn.BatchNorm1d(1024)
self.fc2 = nn.Linear(1024,256)
self.bn_f2=nn.BatchNorm1d(256)
self.fc3 = nn.Linear(256, 4)
def forward(self,x):
x=self.pool(F.relu(self.conv1(x)))
x=self.bn1(x)
x=self.pool(F.relu(self.conv2(x)))
x = self.bn2(x)
x = self.pool(F.relu(self.conv3(x)))
x = self.bn3(x)
x=self.drop2d(x)
x=x.view(-1,x.size(1)*x.size(2)*x.size(3)) #将数据展平,因为接下来要输入全连接层。
x=F.relu(self.fc1(x))
x=self.bn_f1(x)
x=self.drop(x)
x = F.relu(self.fc2(x))
x = self.bn_f2(x)
x = self.drop(x)
x=self.fc3(x)
return x
注:一个模型的可学习参数可以通过`net.parameters()`返回
3.模型的初始化和加载到GPU
model=Net()
if torch.cuda.is_available():#判断GPU是否可用
model.to('cuda')
4.损失函数
二分类交叉熵损失函数 torch.nn.BCELoss | 计算二分类任务时的交叉熵(Cross Entropy)函数 |
交叉熵损失函数torch.nn.CrossEntropyLoss | 计算交叉熵函数 |
L1损失函数torch.nn.L1Loss | 计算输出`y`和真实标签`target`之间的差值的绝对值 |
MSE损失函数torch.nn.MSELoss | 计算输出`y`和真实标签`target`之差的平方。 |
平滑L1损失函数torch.nn.SmoothL1Loss | L1的平滑输出,其功能是减轻离群点带来的影响 |
目标泊松分布的负对数似然损失torch.nn.PoissonNLLLoss | 泊松分布的负对数似然损失函数 |
KL散度torch.nn.KLDivLoss | 用于连续分布的距离度量,并且对离散采用的连续输出空间分布进行回归通常很有用 |
torch.nn.MarginRankingLoss | 计算两个向量之间的相似度,用于排序任务。该方法用于计算两组数据之间的差异 |
多标签边界损失函数torch.nn.MultiLabelMarginLoss | 对于多标签分类问题计算损失函数 |
二分类损失函数torch.nn.SoftMarginLoss | 计算二分类的 logistic 损失 |
多分类的折页损失torch.nn.MultiMarginLoss | 计算多分类的折页损失 |
注意:在使用Cross Entropy损失时,模型不需要加softmax层,因为PyTorch会自动把整数型的label转为one-hot型,用于计算BCE loss。
5.优化器
优化器是根据网络反向传播的梯度信息来更新网络的参数,以起到降低loss函数计算值,使得模型输出更加接近真实标签。
优化器常用操作:
- step():执行一步梯度更新,参数更新
- zero_grad():清空所管理参数的梯度,PyTorch的特性是张量的梯度不自动清零,因此每次反向传播后都需要清空梯度。
- load_state_dict() :加载状态参数字典,可以用来进行模型的断点续训练,继续上次的参数进行训练。
- state_dict():获取优化器当前状态信息。
优化器中的学习速率衰减:
#学习速率(lr)衰减
from torch.optim import lr_scheduler
#按步长衰减。参数分别 对应为:要衰减的优化器、衰减步长、衰减系数
exp_lr_scheduler=lr_scheduler.StepLp(optimizer,step_size=5,gramma=0.1)
#以列表的形式说哪几步衰减,其中参数milestones表示在哪几步进行衰减
lr_scheduler.MultiStepLR(optimizer,milestones,gramma=0.1)
#在每一个epoch上进行速率衰减,知道最后一个
lr_scheduler.ExponentialLR(optimizer,gramma=0.1)
在使用时直接在训练的循环中加入exp_lr_scheduler.step()即可。
6.模型的训练和测试
首先应该先设置模型的状态:1.model.train(),表示训练状态,一般在有dropout层和Batch Normalization层时会发挥作用。2.model.evil(),表示预测/测试状态,此时不需要修改模型的参数,所以对应的dropout层和BatchNormalization层也不会发生作用。然后就可以进行正常的训练了。
#模型的训练(model.train())
for image, label in train_loader: #读取DataLoader中的数据,数据会按批次输出
image, label = image.to('cuda'), label.to('cuda') #将数据放到GPU上用于后续计算
pred = model(image) #将数据送入模型中训练
optimizer.zero_grad() #在开始用当前批次数据做训练之前,应当先将优化器的梯度置零:
loss = loss_fn(pred, label)#根据预先定义的损失函数计算损失值
loss.backward() #将loss反向传播回网络
optimizer.step() #使用优化器更新模型参数
模型的测试过程基本和训练过程差不多,不同之处在于:1.在模型测试阶段需要先调用model.eval();2.预先设置torch.no_grad;3.模型测试阶段不需要改变模型的参数,所以不需要优化器,和误差反向传播。
(二)基础实战-----------FashionMNIST时装分类
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
import torchvision
from torchvision import datasets, transforms
from torchvision.transforms import ToTensor
import copy
import os #python标准库,无需安装
import shutil
train_ds=datasets.FashionMNIST('data', train=True, transform=ToTensor(), download=True)
test_ds=datasets.FashionMNIST('data', train=False, transform=ToTensor(), download=True)
train_dl=torch.utils.data.DataLoader(train_ds, batch_size=64, shuffle=True)
test_dl=torch.utils.data.DataLoader(test_ds, batch_size=64)
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1=nn.Conv2d(1,16,3)
self.bn1 = nn.BatchNorm2d(16)
self.conv2 = nn.Conv2d(16, 32, 3)
self.bn2 = nn.BatchNorm2d(32)
self.pool=nn.MaxPool2d(2,2)
self.drop=nn.Dropout(0.5)
self.fc1=nn.Linear(32*5*5,1024)
self.bn_f1=nn.BatchNorm1d(1024)
self.fc2 = nn.Linear(1024,256)
self.bn_f2=nn.BatchNorm1d(256)
self.fc3 = nn.Linear(256, 10)
def forward(self,x):
x=self.pool(F.relu(self.conv1(x)))
x=self.bn1(x)
x=self.pool(F.relu(self.conv2(x)))
x = self.bn2(x)
#print(x.size())
x=x.view(-1,x.size(1)*x.size(2)*x.size(3))
x=F.relu(self.fc1(x))
x=self.bn_f1(x)
x=self.drop(x)
x = F.relu(self.fc2(x))
x = self.bn_f2(x)
x = self.drop(x)
x=self.fc3(x)
return x
model=Net()
if torch.cuda.is_available():#判断GPU是否可用
model.to('cuda')
loss_fn=nn.CrossEntropyLoss()
optim=torch.optim.Adam(model.parameters(),lr=0.001)
def train(dl, model, loss_fn, optimizer):
size=len(dl.dataset)
num_batches=len(dl)
train_loss, correct=0, 0
model.train()
for x, y in dl:
x, y=x.to('cuda'), y.to('cuda')
pred=model(x)
loss=loss_fn(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
with torch.no_grad():
correct +=(pred.argmax(1) == y).type(torch.float).sum().item()#这个1表示维度。pred有两个维度(batch,数据的类型)
train_loss +=loss.item()
correct /= size
train_loss /=num_batches
return correct, train_loss
def test(dl, model, loss_fn):
size=len(dl.dataset)
num_batches=len(dl)
test_loss, correct=0, 0
model.eval()
with torch.no_grad():
for x, y in dl:
x, y=x.to('cuda'), y.to('cuda')
pred=model(x)
loss=loss_fn(pred, y)
test_loss += loss.item()
correct +=(pred.argmax(1) == y).type(torch.float).sum().item()
correct /= size
test_loss /=num_batches
return correct, test_loss
#训练50个epoch
epochs=50
best_model_wts=copy.deepcopy(model.state_dict())
best_acc=0
train_loss=[]
train_acc=[]
test_loss=[]
test_acc=[]
for epoch in range(epochs):
epoch_train_acc, epoch_train_loss=train(train_dl,model,loss_fn,optim)
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
if epoch_test_acc > best_acc:
best_model_wts = copy.deepcopy(model.state_dict())
best_acc=epoch_test_acc
train_acc.append(epoch_train_acc)
train_loss.append(epoch_train_loss)
test_acc.append(epoch_test_acc)
test_loss.append(epoch_test_loss)
template=("epoch: {:2d}, train_Loss: {:.5f}, train_acc:{:.1f}, test_Loss: {:.5f}, test_acc:{:.1f}")
print(template.format(epoch, epoch_train_loss, epoch_train_acc*100, epoch_test_loss,epoch_test_acc*100))
print('Done')
print("best_acc: {:.5f}", best_acc)
plt.plot(range(epoch+1),train_acc,label='train_accurate')
plt.plot(range(epoch+1),test_acc,label='test_accurate')
plt.legend()
plt.show()
结果图: