PyTorch实践系列(二):GPU与CPU运行对比

写在开头:本节其实在学习了上一节的MNIST手写体分类后,自己又按照VGG的迷你版搭建了一遍网络,再运行的时候突然想到可以用GPU来跑,于是本节就GPU与CPU运算速度进行一个对比。

内容安排

今天主要是简单介绍一下VGG16网络架构还有如何将Torch代码放到GPU上运行,然后比较GPU与CPU运行的花费时间,因为采用的是MNIST数据集所以没办法搭建完整的VGG16网络,就按个迷你版的把。

正文开始

1.VGG16网络介绍
其实VGG16和VGG19的介绍网上还是挺多的,我们主要是想说一下他们怎么调用的层就行了,然后我们使用了哪些部分。
VGG-16的网络结构如下图所示,由13个卷积层和3个全连接层构成,并且在每个卷积层和全连接层后加上Relu处理,卷积核使用的是3维卷积核,图片来源于网络,
在这里插入图片描述
然后我们简单的介绍一些概念,这在之前的Torch系列1里有介绍我们卷积的过程以及RELU函数,还有池化层。那为什么需要这些操作呢?
Q1:为什么需要池化
在卷积之后会提取很多特征信息,但由于卷积核的滑动,相邻区域的特征信息是相似的,可以相互替代的,为了增加计算效率,降低信息维度,就可以使用池化进行降维,一般使用的是2维卷积核,步长也为2这样就会提取到尽量少的重复信息。
Q2:为什么需要ReLU激活函数
主要原因有以下几点,
1)因为如果没有ReLU的话,那么我们的矩阵计算永远都是线性运算,无法用非线性的方法去估计到目标值;
2)可以引入稀疏性,减少了参数的相互依存关系,缓解了过拟合问题的发生。
3)ReLU函数相对于sigmoid函数不具有正向的饱和性,不会在训练次数增加后接近饱和降低运行速度。
4)ReLU函数求导非常容易,效率很高。
Q3:为什么需要全连接层
将卷积层进行拉伸向量化,然后与一个参数矩阵相乘,变成一个低纬度的向量,最后得到与类别数相同的维度向量。那么为什么会需要从全连接层到卷积层又到全连接层的操作呢?
为什么需要全连接层到卷积层的操作?
1)向前传播时效率更高,根据CS2231课程的描述认为,全连接层编程卷积后能够在某些场景提升效率,比如需要让卷积网络在一张更高维度的输入图片进行分块滑动,得到多个输出。或者说在每次池化后会大大提升速度。
2)会使得更加的方便,通过设置步长已经卷积核维度或者padding,能够很轻易的得到我们希望得到的图片维度,不需要修改网络参数,直接可以接受多个维度的图片。
为什么卷积层还要转为全连接层呢?
因为最终的输出是一个预测类的结果,全连接层的作用就是将学到的“分布式特征表示”映射到样本标记空间的作用,起到一个分类器的作用。参考观点源于这里。但目前由于全连接层参数冗余的问题,几乎占神经网络80%的参数,有很多使用全局平均池化GAP去替代FC全连接层。但在参考文献中又指出及时FC目前作用被普遍看低但仍然有作用。这里我会在后面学习后再来分析。
2.搭建迷你版VGG
下面我们来展示一下我们搭建的一个迷你版VGG网络,代码如下,

class Cnet(torch.nn.Module):
    def __init__(self):
        super(Cnet, self).__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(1, 64, 3, 1, 1),
            nn.Conv2d(64, 64, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(64, 128, 3, 1, 1),
            nn.Conv2d(128, 128, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )

        self.fc = nn.Sequential(
            nn.Linear(7*7*128, 4096),
            nn.ReLU(),
            nn.Dropout(),
            nn.Linear(4096, 1000),
            nn.Linear(1000, 10),
        )
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = x.view(-1, 7*7*128)
        x = self.fc(x)
        return F.log_softmax(x)

3.如何在GPU上运行?
这里需要将网络,损失函数,以及数据都转为cuda里就行了,不过第一步得看看,是否可用GPU运行。

torch.cuda.is_available()

如果返回True那就可以了,如果是False那就不能使用GPU,也可以手动设置为False不使用GPU。下面就需要把模型损失函数以及数据都转到cuda里,参考文献见这里
举个例子,对网络和损失函数放入cuda,

network = Cnet()
loss_func = torch.nn.NLLLoss()
if(use_gpu):
    network = network.cuda()
    loss_func = loss_func.cuda()

然后把数据放入到GPU上,

x,y = batch_x.cuda(),batch_y.cuda()

最后调用loss时可以使用,

loss = loss.cpu()

注意我们是将训练的过程放到GPU但是在输出的时候还是会将数据放回CPU上进行输出,那么GPU与CPU的读写速度差异就是我们运行结果的时间延误。
Q1:为什么需要在GPU与CPU上来回切换
我们可以理解为我们的数据是Numpy,Numpy只能再CPU上运行,那么为了在GPU上跑一跑,就需要将Numpy转换为Tensor,但为了记录梯度,我们还需要将Tensor转为Variable。这样就能够使用GPU加速。同样我们运行完后需要取出数据就得先将Variable转为Tensor然后再把Tensor转为Numpy进行输出。
4.实际运行对比
因为读取数据、网络结构和调用的代码时一样的,代码如下,
加载数据

import torch
import torchvision


batch_size_train = 64
batch_size_test = 1000
learning_rate = 0.01
momentum = 0.9
n_epochs = 3
log_interval = 10 #控制可视化输出间隔

random_seed = 1 
#是否使用非确定性算法,True时会自动寻找适合当前的高效算法。
torch.backends.cudnn.enabled = False 
torch.manual_seed(random_seed)

train_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('/files/', train=True,
                                transform=torchvision.transforms.Compose(
                                    [torchvision.transforms.ToTensor(),
                                    torchvision.transforms.Normalize((0.1307,),(0.3081,))]
                                )),
    batch_size=batch_size_train, shuffle=True
)

test_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('/files/', train=False, download=True,
                                transform=torchvision.transforms.Compose(
                                    [
                                        torchvision.transforms.ToTensor(),
                                        torchvision.transforms.Normalize((0.1307,),(0.3081,))
                                    ]
                                )),
    batch_size=batch_size_test, shuffle=True
)

example = enumerate(train_loader)
bacth_idx, (example_data, example_targets) = next(example)
example_data.shape

网路搭建

import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

class Cnet(torch.nn.Module):
    def __init__(self):
        super(Cnet, self).__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(1, 64, 3, 1, 1),
            nn.Conv2d(64, 64, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(64, 128, 3, 1, 1),
            nn.Conv2d(128, 128, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )

        self.fc = nn.Sequential(
            nn.Linear(7*7*128, 4096),
            nn.ReLU(),
            nn.Dropout(),
            nn.Linear(4096, 1000),
            nn.Linear(1000, 10),
        )
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = x.view(-1, 7*7*128)
        x = self.fc(x)
        return F.log_softmax(x)

调用代码

import time

starttime = time.time()
time.sleep(2.1) #延时2.1s
for i in range(1, n_epochs+1):
    train(i)
    test()
endtime = time.time()
dtime = endtime - starttime

print("程序运行时间:%.8s s" % dtime)

下面分别是CPU与GPU的训练和测试的代码,
CPU训练测试代码,

network = Cnet()
optimizer = optim.SGD(network.parameters(), lr = learning_rate, momentum=momentum)
loss_func = torch.nn.NLLLoss()
train_loss_his = []
test_loss_his = []
def train(epoch):
    network.train()
    for i, (batch_x, batch_y) in enumerate(train_loader):
        output = network(batch_x)
        loss = loss_func(output, batch_y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if i % 100 == 0:
            print('Epoch|%d, loss|%f'%(epoch,loss.item()))
            train_loss_his.append(loss.item())
def test():
    network.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for batch_x, batch_y in test_loader:
            output = network(batch_x)
            test_loss += F.nll_loss(output, batch_y, size_average=False).item()
            pred = output.data.max(1, keepdim=True)[1]
            correct += pred.eq(batch_y.data.view_as(pred)).sum()
        test_loss /= len(test_loader.dataset)
        print('loss|%.4f, pred|%.5f'%(test_loss, 100. * correct / len(test_loader.dataset)))

GPU训练测试代码,


network = Cnet()
network = network.cuda()
optimizer = optim.SGD(network.parameters(), lr = learning_rate, momentum=momentum)
loss_func = torch.nn.NLLLoss()
loss_func = loss_func.cuda()
train_loss_his = []
test_loss_his = []
def train(epoch):
    network.train()
    for i, (batch_x, batch_y) in enumerate(train_loader):
        x = batch_x.cuda()
        y = batch_y.cuda()
        output = network(x)
        loss = loss_func(output, y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        loss = loss.cpu()

        if i % 100 == 0:
            print('Epoch|%d, loss|%f'%(epoch,loss.item()))
            train_loss_his.append(loss.item())
def test():
    network.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for batch_x, batch_y in test_loader:
            x = batch_x.cuda()
            y = batch_y.cuda()
            output = network(x)
            loss = loss_func(output, y)
            test_loss += loss.cpu().item()
            pred = output.data.max(1, keepdim=True)[1]
            correct += pred.eq(y.data.view_as(pred)).sum()
        test_loss /= len(test_loader.dataset)
        print('loss|%.4f, pred|%.5f'%(test_loss, 100. * correct / len(test_loader.dataset)))

于是分别运行CPU代码与GPU代码,得到运行结果如下,
CPU运行时间
在这里插入图片描述
GPU运行时间
在这里插入图片描述
可以看到速度是了10倍差不多,这还只是调用的单个GPU加速,如果调用多个GPU可能会更快,这些我会在后面学习了后再放上来。


结语
今天关于GPU与CPU运行的代码到这里就结束了,对前沿和一些细节还没有把握好,会在后面进行学习更新的。
谢谢阅读。

猜你喜欢

转载自blog.csdn.net/qq_35149632/article/details/105236545
今日推荐