建议初学者在开始实践各种Eaxmple之前,先把这篇博客看一下,coding细节不用看,主要是了解一下Pytorch构建神经网络是一个怎样的流程,大佬请忽略,小的不才,还请多多指教!!!
import torch
import torch.nn as nn # 各种层类型的实现
import torch.nn.functional as F # 各中层函数的实现,与层类型对应,如:卷积函数、池化函数、归一化函数等等
import torch.optim as optim # 实现各种优化算法的包
from torchvision import datasets, transforms
自定义模型类需要继承nn.Module
,且你至少要重写__init__
和forward
两个函数。
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
""" 初始化层类型
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.conv2_drop = nn.Dropout2d()
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
"""
def forward(self, x):
""" 定义前向传播
x = F.relu(F.max_pool2d(self.conv1(x), 2))
x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
x = x.view(-1, 320) # 类似于np.reshape
x = F.relu(self.fc1(x))
x = F.dropout(x, training=self.training)
x = self.fc2(x)
return F.log_softmax(x, dim=1)
"""
model = Net().to(device) # 实例化自定义网络模型
顺序容器,模型将按照它们在构造函数中传递的顺序添加到容器中 或者 也可以传入模型的有序字典。
# Example of using Sequential
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)
# Example of using Sequential with OrderedDict
model = nn.Sequential(OrderedDict([
('conv1', nn.Conv2d(1,20,5)),
('relu1', nn.ReLU()),
('conv2', nn.Conv2d(20,64,5)),
('relu2', nn.ReLU())
]))
网络模型构建好之后,开始要定义一个训练函数def train()
,采用函数定义的方式,代码规范,可读性强,下面附上常见套路,不见得非要一致,作为参考:
def train(args, model, device, train_loader, optimizer, epoch): # 还可添加loss_func等参数
model.train() # 必备,将模型设置为训练模式
for batch_idx, (data, target) in enumerate(train_loader): # 从数据加载器迭代一个batch的数据
data, target = data.to(device), target.to(device) # 将数据存储CPU或者GPU
optimizer.zero_grad() # 清除所有优化的梯度
output = model(data) # 喂入数据并前向传播获取输出
loss = F.nll_loss(output, target) # 调用损失函数计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
if batch_idx % args.log_interval == 0: # 根据设置的显式间隔输出训练日志
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))
def test(args, model, device, test_loader):
model.eval() # 必备,将模型设置为评估模式
test_loss = 0
correct = 0
with torch.no_grad(): # 禁用梯度计算
for data, target in test_loader: # 从数据加载器迭代一个batch的数据
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.nll_loss(output, target, reduction='sum').item() # sum up batch loss
pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability
correct += pred.eq(target.view_as(pred)).sum().item() # 统计预测正确个数
test_loss /= len(test_loader.dataset)
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))
模型构建好了,训练和测试函数也定义好了,接下来就是开始训练和测试,也是建议定义一个主函数def main()
,基本流程如下:
下面以手写字体为例:
def main():
# Training settings
parser = argparse.ArgumentParser(description='PyTorch MNIST Example')
parser.add_argument('--batch-size', type=int, default=64, metavar='N',
help='input batch size for training (default: 64)')
parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N',
help='input batch size for testing (default: 1000)')
parser.add_argument('--epochs', type=int, default=10, metavar='N',
help='number of epochs to train (default: 10)')
parser.add_argument('--lr', type=float, default=0.01, metavar='LR',
help='learning rate (default: 0.01)')
parser.add_argument('--momentum', type=float, default=0.5, metavar='M',
help='SGD momentum (default: 0.5)')
parser.add_argument('--no-cuda', action='store_true', default=False,
help='disables CUDA training')
parser.add_argument('--seed', type=int, default=1, metavar='S',
help='random seed (default: 1)')
parser.add_argument('--log-interval', type=int, default=10, metavar='N',
help='how many batches to wait before logging training status')
args = parser.parse_args()
use_cuda = not args.no_cuda and torch.cuda.is_available() # 根据输入参数和实际cuda的有无决定是否使用GPU
torch.manual_seed(args.seed) # 设置随机种子,保证可重复性
device = torch.device("cuda" if use_cuda else "cpu") # 设置使用CPU or GPU
kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {} # 设置数据加载的子进程数;是否返回之前将张量复制到cuda的页锁定内存
train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=args.batch_size, shuffle=True, **kwargs)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=args.test_batch_size, shuffle=True, **kwargs)
model = Net().to(device) # 实例化网络模型
optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum) # 实例化求解器
for epoch in range(1, args.epochs + 1): # 循环调用train() and test() 进行epoch迭代
train(args, model, device, train_loader, optimizer, epoch)
test(args, model, device, test_loader)
网络的保存和提取有两种方式:一种是保存网络的状态(parameters and buffers);另一种是保存整个网络(模型和状态),当然是推荐第一种啦!!!(方便重构)。主要是用到一下几个方法:
# 序列化(将对象保存到磁盘文件)
torch.save(obj, f, ...) # f:类文件对象(必须实现read,readline,tell和seek),或包含文件名的字符串
# 反序列化
torch.load(f, map_location=None, ...) # map_location:一个函数,torch.device,string或dict,指定如何重新映射存储位置
# 将state_dict中的参数和缓冲区复制到模型中
torch.nn.Module.load_state_dict(state_dict, strict=True) # strict设置是否匹配由state_dict()函数返回的键
# 返回包含模型完整状态的字典
torch.nn.Module.state_dict(destination=None, prefix='', keep_vars=False)
model.eval()
将dropout和batch normalization层设置为evaluation(评估)模式!!!the_model = TheModelClass(*args, **kwargs)
torch.save(the_model.state_dict(), PATH)
the_model.load_state_dict(torch.load(PATH))
model.eval()
这种方式仅看文档的写法,会给你造成一个误解,认为整个模型保存下来了,任何地方都能直接load使用,其实不然!!!
这种方法的缺点是序列化数据绑定到特定类以及保存模型时使用的确切目录结构。 原因是因为pickle不保存模型类本身。 相反,它会保存包含类的文件的路径,该文件在加载时使用。 因此,当您在其他项目中或在重构之后使用时,代码可能会以各种方式中断,属性错误之类的。
the_model = TheModelClass(*args, **kwargs)
torch.save(the_model, PATH)
# Model class must be defined somewhere
the_model = torch.load(PATH)
model.eval()
虽然在torchvision
中包含了目前流行的数据集,实际上我们经常要使用自己的数据,下面又是如何加载自己的图像数据集的套路!!!
所有
datasets
都是torch.utils.data.Dataset
的子类,即它们实现了__getitem__
和__len__
方法。 因此,它们都可以传递给torch.utils.data.DataLoader
,且可以使用torch.multiprocessing
来并行加载多个样本。
这里很重要!!! 从上可知,一般需要先定义一个代表数据集的抽象子类datasets
,再用torch.utils.data.DataLoader
来迭代数据。 注意:子类必须重写__len__
(提供数据集大小)和__getitem__
(支持整型索引)!
datasets
# 子类定义
class torchvision.datasets.ImageFolder(root, transform=None, target_transform=None, loader=<function default_loader>)
# 方法
__getitem__(index) # 返回值:(sample, target)
# 子类定义和ImageFolder类似,自己点SOURCE进手册查看!!!
自定义好代表数据集的抽象子类之后,使用torch.utils.data.DataLoader
迭代数据(组合datasets
和采样器,并在数据集上提供单进程或多进程迭代器)。
# Definition:
class torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=<function default_collate>, pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None)
# Parameters:
dataset: 定义的抽象数据集
# 嗯。。。太多了,其实看名字就大概知道意思,不写了,想看查手册就好!
有些数据集没有按类放不同的文件夹而是给了标签和图像名,这种情况可以像Caffe加载图像数据一样,提供一个txt文件,里面每一行含有图片路径和标签,按上面所介绍的流程先写一个抽象子类,代码如下:
(逃 。。。拿官例里面的源码改了一下,有错勿喷,水平不行!)
# 子类定义 和上面介绍有点小区别,多了一个图像路径参数,因为我的txt中只有文件名!!!
import torch.utils.data as data
import torchvision.transforms as transforms
from os import listdir
from os.path import join
from PIL import Image
def image_file_name(txtfile,image_dir):
image_filenames = []
image_classes = []
with open(txtfile,'r') as f:
for line in f:
image_filenames.append(image_dir+line.split()[0])
image_classes.append(int(line.split()[1]))
return image_filenames,image_classes
def load_img(filepath):
with open(filepath, 'rb') as f:
img = Image.open(filepath)
return img.convert('RGB')
class DatasetFromTxt(data.Dataset):
def __init__(self, txtfile, image_dir, input_transform=None, target_transform=None):
super(DatasetFromTxt, self).__init__()
self.image_filenames, self.image_classes = image_file_name(txtfile, image_dir)
self.input_transform = input_transform
self.target_transform = target_transform
def __getitem__(self, index):
input = load_img(self.image_filenames[index])
target = self.image_classes[index]
if self.input_transform:
input = self.input_transform(input)
if self.target_transform:
target = self.target_transform(target)
return input, target
def __len__(self):
return len(self.image_filenames)