【电子羊的奇妙冒险】初试深度学习(1)

最近忙于浩繁的学习任务,深感知识体系庞大,而面向百度学习又免不了亦步亦趋的情况,于是特开一个板块用于记录学习历程,也顺便作为笔记,适时阶段性总结。

环境配置

配置清单

  • 硬件:联想拯救者(GTX 1050ti)
  • 系统:Ubuntu 20.04 64位

首先,因为《优雅的使用Matlab进行机器学习》这门课需要在课堂上使用Matlab且连上三节,我的小小游戏本是无法实现开着Matlab再续航三小时的,于是入手了小新14pro,承担上课、写代码以及部分稿件写作的功能。于是拯救者闲置在寝室,为了物尽其用,同时也为了更好地适配pytorch和cuda,重装系统,并安装了双系统(windows10+Ubuntu20.04),让linux从虚拟机中解放。
总之,经过一系列操作,我的学习主力逐渐由win向linux转移。

OK,现在是软件部分。
主要安装的程序如下:

  • nvidia
  • cuda
  • pytorch

!!注意:cuda和tensorflow都对软件适配版本有要求,也就是说,经常出现一方软件更新版本而另一方还未适配的情况。如果软件安装好了却一直无法调用,很有可能是通过不同路径下载安装的软件版本不适配,这种情况下最好删掉重新找路径安装,以免之后出现问题。

nvidia驱动安装

cuda的环境配置主要来源于这位博主的博客:https://blog.csdn.net/victoryaoyu/article/details/70034569?spm=1001.2014.3001.5506
也可参见官方文档

https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#axzz4dqv9aEUn

对于版本适配的问题,我的建议是:

  1. 根据硬件(显卡)确定nvidia版本
  2. 依据系统版本(ubuntu)和nvidia版本确定cuda版本
  3. 最后按照pytorch官网的配置建议下载pytorch

在这里插入图片描述
验证GPU是否支持CUDA:

$ lspci | grep -i nvidia

验证linux支持:

$ uname -m && cat /etc/*release

验证gcc:

$ gcc --version

若未安装则使用一下命令:

sudo apt-get  install  build-essential

验证系统已经安装了正确的 Kernel Headers和Development Packages:

$ uname -r
$ sudo apt-get install linux-headers-$(uname -r)

nvidia驱动我的建议是换源从官网下,可以选择对应版本。

安装好后重启,终端输入

nvidia -smi

查看显卡版本信息

cuda安装

这个同样建议在官网对着版本一个一个下

https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=20.04&target_type=deb_network

在这里插入图片描述
我的版本如上图,所以终端输入命令行如下:

wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/7fa2af80.pub
sudo add-apt-repository "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/ /"
sudo apt-get update
sudo apt-get -y install cuda

在这里插入图片描述

验证CUDA Toolkit,在终端中输入以下命令:

$ nvcc -V

pytorch

官网链接如下:

https://pytorch.org/get-started/locally/

我用的命令行是:

conda install pytorch torchvision torchaudio cudatoolkit=10.2 -c pytorch

安装后,输入下列命令:

from __future__ import print_function
import torch
x = torch.rand(5, 3)
print(x)

输出结果类似下面的结果即安装成功:

tensor([[0.3380, 0.3845, 0.3217],
        [0.8337, 0.9050, 0.2650],
        [0.2979, 0.7141, 0.9069],
        [0.1449, 0.1132, 0.1375],
        [0.4675, 0.3947, 0.1426]])

验证能否正确运行在 GPU 上:

import torch
torch.cuda.is_available()

如果返回 True,就可以运行,否则就不能。
如果不能正常运行,首先检测版本是否对应一致,若不一致,优先考虑将不适配版本彻底删除重装,省心(血泪教训)。

pytorch小试

我用的是pycharm,因此有的库需要提前导入,这个好办,运行的时候会提醒你导入哪些,按照提示做就行了。

from __future__ import print_function
import torch

以下内容来源于pytorch官方文档:

声明和定义

torch.empty(): 声明一个未初始化的矩阵

# 创建一个 5*3 的矩阵
x = torch.empty(5, 3)
print(x)

输出如下:

tensor([[9.2737e-41, 8.9074e-01, 1.9286e-37],
        [1.7228e-34, 5.7064e+01, 9.2737e-41],
        [2.2803e+02, 1.9288e-37, 1.7228e-34],
        [1.4609e+04, 9.2737e-41, 5.8375e+04],
        [1.9290e-37, 1.7228e-34, 3.7402e+06]])

长得差不多就行了,检验torch基本功能。
torch.rand():随机初始化一个矩阵

# 创建一个随机初始化的 5*3 矩阵
rand_x = torch.rand(5, 3)
print(rand_x)

torch.zeros():创建数值皆为 0 的矩阵

# 创建一个数值皆是 0,类型为 long 的矩阵
zero_x = torch.zeros(5, 3, dtype=torch.long)
print(zero_x)

输出:

tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])

torch.tensor():直接传递 tensor 数值来创建

# tensor 数值是 [5.5, 3]
tensor1 = torch.tensor([5.5, 3])
print(tensor1)

输出:

tensor([5.5000, 3.0000])

操作(Operations)

详见官方文档:

https://pytorch.org/docs/stable/torch.html

  • 运算符
    torch.add(tensor1, tensor2, [out=tensor3])
    tensor1.add_(tensor2):直接修改 tensor 变量
tensor4 = torch.rand(5, 3)
print('tensor3 + tensor4= ', tensor3 + tensor4)
print('tensor3 + tensor4= ', torch.add(tensor3, tensor4))
# 新声明一个 tensor 变量保存加法操作的结果
result = torch.empty(5, 3)
torch.add(tensor3, tensor4, out=result)
print('add result= ', result)
# 直接修改变量
tensor3.add_(tensor4)
print('tensor3= ', tensor3)

输出:

tensor3 + tensor4=  tensor([[ 0.1000,  0.1325,  0.0461],
        [ 0.4731,  0.4523, -0.7517],
        [ 0.2995, -0.9576,  1.4906],
        [ 1.0461,  0.7557, -0.0187],
        [ 2.2446, -0.3473, -1.0873]])

tensor3 + tensor4=  tensor([[ 0.1000,  0.1325,  0.0461],
        [ 0.4731,  0.4523, -0.7517],
        [ 0.2995, -0.9576,  1.4906],
        [ 1.0461,  0.7557, -0.0187],
        [ 2.2446, -0.3473, -1.0873]])

add result=  tensor([[ 0.1000,  0.1325,  0.0461],
        [ 0.4731,  0.4523, -0.7517],
        [ 0.2995, -0.9576,  1.4906],
        [ 1.0461,  0.7557, -0.0187],
        [ 2.2446, -0.3473, -1.0873]])

tensor3=  tensor([[ 0.1000,  0.1325,  0.0461],
        [ 0.4731,  0.4523, -0.7517],
        [ 0.2995, -0.9576,  1.4906],
        [ 1.0461,  0.7557, -0.0187],
        [ 2.2446, -0.3473, -1.0873]])

和 Numpy 数组的转换

Tensor 转换为 Numpy 数组:

a = torch.ones(5)
print(a)
b = a.numpy()
print(b)

输出:

tensor([1., 1., 1., 1., 1.])
[1. 1. 1. 1. 1.]

Numpy 数组转换为 Tensor
转换的操作是调用 torch.from_numpy(numpy_array) 方法。例子如下所示:

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b)

输出:

[2. 2. 2. 2. 2.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)

CUDA 张量

!!!很重要!!!
Tensors 可以通过 .to 方法转换到不同的设备上,即 CPU 或者 GPU 上。例子如下所示:

# 当 CUDA 可用的时候,可用运行下方这段代码,采用 torch.device() 方法来改变 tensors 是否在 GPU 上进行计算操作
if torch.cuda.is_available():
    device = torch.device("cuda")          # 定义一个 CUDA 设备对象
    y = torch.ones_like(x, device=device)  # 显示创建在 GPU 上的一个 tensor
    x = x.to(device)                       # 也可以采用 .to("cuda") 
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # .to() 方法也可以改变数值类型

输出结果,第一个结果就是在 GPU 上的结果,打印变量的时候会带有 device=‘cuda:0’,而第二个是在 CPU 上的变量。

tensor([1.4549], device='cuda:0')
tensor([1.4549], dtype=torch.float64)

详见文档

https://pytorch.org/tutorials/beginner/blitz/tensor_tutorial.html

神经网络

本节文档:

https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html#sphx-glr-beginner-blitz-neural-networks-tutorial-py

在 PyTorch 中 torch.nn 专门用于实现神经网络。其中 nn.Module 包含了网络层的搭建,以及一个方法-- forward(input) ,并返回网络的输出 outptu .

下面是一个经典的 LeNet 网络,用于对字符进行分类。
在这里插入图片描述
对于神经网络来说,一个标准的训练流程是这样的:

  • 定义一个多层的神经网络
  • 对数据集的预处理并准备作为网络的输入
  • 将数据输入到网络
  • 计算网络的损失
  • 反向传播,计算梯度
  • 更新网络的梯度,一个简单的更新规则是 weight = weight - learning_rate * gradient
定义网络

首先定义一个神经网络,下面是一个 5 层的卷积神经网络,包含两层卷积层和三层全连接层:

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

class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 输入图像是单通道,conv1 kenrnel size=5*5,输出通道 6
        self.conv1 = nn.Conv2d(1, 6, 5)
        # conv2 kernel size=5*5, 输出通道 16
        self.conv2 = nn.Conv2d(6, 16, 5)
        # 全连接层
        self.fc1 = nn.Linear(16*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # max-pooling 采用一个 (2,2) 的滑动窗口
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # 核(kernel)大小是方形的话,可仅定义一个数字,如 (2,2) 用 2 即可
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        # 除了 batch 维度外的所有维度
        size = x.size()[1:]
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

net = Net()
print(net)

打印网络结构:

Net(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

在这里插入图片描述

这里必须实现 forward 函数,而 backward 函数在采用 autograd 时就自动定义好了,在 forward 方法可以采用任何的张量操作。

net.parameters() 可以返回网络的训练参数,使用例子如下:

params = list(net.parameters())
print('参数数量: ', len(params))
# conv1.weight
print('第一个参数大小: ', params[0].size())

输出:

参数数量:  10
第一个参数大小:  torch.Size([6, 1, 5, 5])

然后简单测试下这个网络,随机生成一个 32*32 的输入:

# 随机定义一个变量输入网络
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

输出结果:

tensor([[ 0.1005,  0.0263,  0.0013, -0.1157, -0.1197, -0.0141,  0.1425, -0.0521,
          0.0689,  0.0220]], grad_fn=<ThAddmmBackward>)
训练分类器
标题训练数据

在训练分类器前,当然需要考虑数据的问题。通常在处理如图片、文本、语音或者视频数据的时候,一般都采用标准的 Python 库将其加载并转成 Numpy 数组,然后再转回为 PyTorch 的张量。

对于图像,可以采用 Pillow, OpenCV 库;
对于语音,有 scipy 和 librosa;
对于文本,可以选择原生 Python 或者 Cython 进行加载数据,或者使用 NLTK 和 SpaCy 。
PyTorch 对于计算机视觉,特别创建了一个 torchvision 的库,它包含一个数据加载器(data loader),可以加载比较常见的数据集,比如 Imagenet, CIFAR10, MNIST 等等,然后还有一个用于图像的数据转换器(data transformers),调用的库是 torchvision.datasets 和 torch.utils.data.DataLoader 。

在本教程中,将采用 CIFAR10 数据集,它包含 10 个类别,分别是飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船和卡车。数据集中的图片都是 3x32x32。一些例子如下所示:在这里插入图片描述

训练图片分类器

训练流程如下:

  1. 通过调用 torchvision 加载和归一化 CIFAR10 训练集和测试集;
  2. 构建一个卷积神经网络;
  3. 定义一个损失函数;
  4. 在训练集上训练网络;
  5. 在测试集上测试网络性能。

加载和归一化 CIFAR10
首先导入必须的包

import torch
import torchvision
import torchvision.transforms as transforms

torchvision 的数据集输出的图片都是 PILImage ,即取值范围是 [0, 1] ,这里需要做一个转换,变成取值范围是 [-1, 1] , 代码如下所示:

# 将图片数据从 [0,1] 归一化为 [-1, 1] 的取值范围
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

这里下载好数据后,可以可视化部分训练图片,代码如下:

import matplotlib.pyplot as plt
import numpy as np

# 展示图片的函数
def imshow(img):
    img = img / 2 + 0.5     # 非归一化
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()



# 随机获取训练集图片
dataiter = iter(trainloader)
images, labels = dataiter.next()

# 展示图片
imshow(torchvision.utils.make_grid(images))
# 打印图片类别标签
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

展示图片如下所示:
在这里插入图片描述
其类别标签为:

frog plane dog ship

实战:
在这里插入图片描述

构建一个卷积神经网络

这部分内容其实直接采用上一节定义的网络即可,除了修改 conv1 的输入通道,从 1 变为 3,因为这次接收的是 3 通道的彩色图片。

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


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


net = Net()
定义损失函数和优化器

这里采用类别交叉熵函数和带有动量的 SGD 优化方法:

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
训练网络

指定需要迭代的 epoch,然后输入数据,指定次数打印当前网络的信息,比如 loss 或者准确率等性能评价标准。

import time
start = time.time()
for epoch in range(2):

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # 获取输入数据
        inputs, labels = data
        # 清空梯度缓存
        optimizer.zero_grad()

        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # 打印统计信息
        running_loss += loss.item()
        if i % 2000 == 1999:
            # 每 2000 次迭代打印一次信息
            print('[%d, %5d] loss: %.3f' % (epoch + 1, i+1, running_loss / 2000))
            running_loss = 0.0
print('Finished Training! Total cost time: ', time.time()-start)

这里定义训练总共 2 个 epoch,训练信息如下,大概耗时为 77s

[1,  2000] loss: 2.226
[1,  4000] loss: 1.897
[1,  6000] loss: 1.725
[1,  8000] loss: 1.617
[1, 10000] loss: 1.524
[1, 12000] loss: 1.489
[2,  2000] loss: 1.407
[2,  4000] loss: 1.376
[2,  6000] loss: 1.354
[2,  8000] loss: 1.347
[2, 10000] loss: 1.324
[2, 12000] loss: 1.311

Finished Training! Total cost time:  77.24696755409241

实战:
在这里插入图片描述

测试模型性能

训练好一个网络模型后,就需要用测试集进行测试,检验网络模型的泛化能力。对于图像分类任务来说,一般就是用准确率作为评价标准。

首先,我们先用一个 batch 的图片进行小小测试,这里 batch=4 ,也就是 4 张图片,代码如下:

dataiter = iter(testloader)
images, labels = dataiter.next()

# 打印图片
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

图片和标签分别如下所示:
在这里插入图片描述

GroundTruth: cat ship ship plane

然后用这四张图片输入网络,看看网络的预测结果

# 网络输出
outputs = net(images)

# 预测结果
_, predicted = torch.max(outputs, 1)
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))

输出为:

Predicted: cat ship ship ship

在这里插入图片描述

前面三张图片都预测正确了,第四张图片错误预测飞机为船
在整个测试集上的准确率:

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))

输出结果如下

Accuracy of the network on the 10000 test images: 55 %

这里可能准确率并不一定一样,教程中的结果是 51% ,因为权重初始化问题,可能多少有些浮动,相比随机猜测 10 个类别的准确率(即 10%),这个结果是不错的,当然实际上是非常不好,不过我们仅仅采用 5 层网络,而且仅仅作为教程的一个示例代码。

然后,还可以再进一步,查看每个类别的分类准确率,跟上述代码有所不同的是,计算准确率部分是 c = (predicted == labels).squeeze(),这段代码其实会根据预测和真实标签是否相等,输出 1 或者 0,表示真或者假,因此在计算当前类别正确预测数量时候直接相加,预测正确自然就是加 1,错误就是加 0,也就是没有变化。

class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs, 1)
        c = (predicted == labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label] += c[i].item()
            class_total[label] += 1


for i in range(10):
    print('Accuracy of %5s : %2d %%' % (classes[i], 100 * class_correct[i] / class_total[i]))

输出结果,可以看到猫、鸟、鹿是错误率前三,即预测最不准确的三个类别,反倒是船和卡车最准确。

Accuracy of plane : 58 %
Accuracy of car : 59 %
Accuracy of bird : 40 %
Accuracy of cat : 33 %
Accuracy of deer : 39 %
Accuracy of dog : 60 %
Accuracy of frog : 54 %
Accuracy of horse : 66 %
Accuracy of ship : 70 %
Accuracy of truck : 72 %

实战:
在这里插入图片描述

在 GPU 上训练

深度学习自然需要 GPU 来加快训练速度的。所以接下来介绍如果是在 GPU 上训练,应该如何实现。

首先,需要检查是否有可用的 GPU 来训练,代码如下:

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)

输出结果如下,这表明你的第一块 GPU 显卡或者唯一的 GPU 显卡是空闲可用状态,否则会打印 cpu 。

cuda:0

既然有可用的 GPU ,接下来就是在 GPU 上进行训练了,其中需要修改的代码如下,分别是需要将网络参数和数据都转移到 GPU 上:

net.to(device)
inputs, labels = inputs.to(device), labels.to(device)

修改后的训练部分代码:

import time
# 在 GPU 上训练注意需要将网络和数据放到 GPU 上
net.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

start = time.time()
for epoch in range(2):

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # 获取输入数据
        inputs, labels = data
        inputs, labels = inputs.to(device), labels.to(device)
        # 清空梯度缓存
        optimizer.zero_grad()

        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # 打印统计信息
        running_loss += loss.item()
        if i % 2000 == 1999:
            # 每 2000 次迭代打印一次信息
            print('[%d, %5d] loss: %.3f' % (epoch + 1, i+1, running_loss / 2000))
            running_loss = 0.0
print('Finished Training! Total cost time: ', time.time() - start)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值