深度学习入门及简单的工程应用

773 阅读8分钟

近期突然兴起,突然想研究研究最近很火(过气)的深度学习。但是作为一个软件工程师,本次研究的主要目标并不是去做算法研究,核心的目标是能够将其用于工程上,所以有些细节及数学原理,本文就不去做深入研究与分析。

本文主要以深度学习的典型应用场景——图像分类为切入点进行讲解。

引子

众所周知,一条直线可以将一个平面分成两半。假设已知直线 ax + by + c = 0,再给你一个点(x1, y1),那相信大家能够很容易地判断出这个点属于这条直线的哪一侧。

那么现在我们对问题做一下转换。给你一堆点,并且告诉你这些点归属于这条直线的哪一侧,现在需要你来求这条直线?

这个问题就很有意思了,恐怕一下子就很难想到如何去解了。一般问题都是公式 + 参数 => 答案,这个问题恰恰相反,变成了参数 + 答案 => 公式。这个问题其实就可以称得上是一个简化版的深度学习原理了。

手写识别

上述的例子,你大概可能看的还有些懵逼。啥,这就深度学习了,我咋啥都没看懂?别急,慢慢来,让我再举一个更直观的例子——手写识别。

mnist

如上图所示,我们的一张图片是由n个像素点组成。简单起见,我们只考虑识别0~9这10个数字,简单认为白色像素为1,黑色像素为0。

思考一个最简单的想法,我们认为每个像素点都有其权重,最终所有白色像素点的权重累加,我们就可以判断出其对应的是哪个数字。

那么我们就可以针对最终识别结果0 ~ 9 写出10个方程。

{0:a1x1+a2x2+...anxn=result0(图片数字为0,则为1,否则为0)1:b1x1+b2x2+...bnxn=result1(图片数字为1,则为1,否则为0)...9:c1x1+c2x2+...cnxn=result9(图片数字为9,则为1,否则为0)\begin{cases} 0: a_{1}x_{1}+a_{2}x_{2}+...a_{n}x_{n} = result_0 (图片数字为0,则为1,否则为0)\\ 1: b_{1}x_{1}+b_{2}x_{2}+...b_{n}x_{n} = result_1 (图片数字为1,则为1,否则为0)\\ ...\\ 9: c_{1}x_{1}+c_{2}x_{2}+...c_{n}x_{n} = result_9 (图片数字为9,则为1,否则为0)\\ \end{cases}

那么对于每张手写图,我们都已知x1,x2,...,xnx_1, x_2, ..., x_n​和resultnresult_n​​​​ ,最终的目标就是求出所有的系数。可以看到这个问题和上面的直线问题就变成同一个形式了。即已知参数和结果求系数

ps: 其实上面的10个方程式转换成图的形式,就是深度学习中的一种经典的网络模型——全连接网络。如下图:

image-20210730164805757

小结

从上面的引子中,我想大家都已经可以比较容易地想到深度学习的基本流程了:

1. 构建公式(构建网络)
2. 使用训练集求出每个公式的系数
3. 使用测试集测试这些系数的准确率

但是如何求解系数,涉及到梯度下降和反向传播等算法。本文中不做详解,可以自行百度。本文直接使用pytorch中提供的现成的接口。

AI框架——pytorch使用

为了快速入门,关于语言的选择,很显然我们就选择python了,AI框架我们选择主流框架pytorch。在pytorch中,我们训练网络的流程基本如下:

  1. 获取数据(训练集和测试集)
  2. 构建网络
  3. 使用训练集进行训练 (求系数)
  4. 使用测试集进行测试

安装

pytorch官网有安装命令,此处就不详解了。当然安装的时候需要注意一下CPU和GPU(CUDA)版本的选择。

获取数据

在AI领域,有很多公开的数据集,例如MNIST——手写数字、CIFAR10和CIFAR100——图像识别、ImageNet——图像识别等。pytorch提供现成的api用于加载这些公开数据集(当然了pytorch也支持自定义数据集)。简单起见,接下来我们以CIFAR10为例进行讲解,CIFAR10总共有10种分类的图片,分别为飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车。

接下来就是pytorch加载数据的代码

import torchvision
import torchvision.transforms as transforms

BATCH_SIZE = 8    # BATCH_SIZE此处为了快速执行,就设定得小一点

# 定义图片转换规则
transform_rule = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# 加载训练集
train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_rule)
train_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True, num_workers=2, drop_last=True)

# 加载测试集
test_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_rule)
test_loader = DataLoader(test_set, batch_size=BATCH_SIZE, shuffle=True, num_workers=2, drop_last=True)

可以看到pytorch直接提供了torchvision.datasets.CIFAR10方法用于下载&加载CIFAR10数据集。

那么我们再看上面,有个图片转换规则。对于pytorch而言,所有的处理都是基于tensor(张量)这种数量类型的,tensor可以简单理解成一个可以运行在GPU上的多维数组。那么我们想要训练数据,就必须先将图片下载后转换成tensor的格式,于是需要调用ToSensor()方法。接下来的Normalize()是归一化,数据预处理用的,不过此处先不管它。

再后面有个DataLoader是用来加载数据(ps: 一般情况下,我们不会一次性把训练集中的所有数据都放进神经网络中进行训练,而是一批一批的进行训练,毕竟数据量大了机器吃不消)。

构建网络

现在数据我们已经有了,接下来就需要构建网络了,在pytorch中,构建网络,我们只需要继承torch.nn.Module,并实现forward()方法即可。如下:

import torch.nn as nn

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(3072, 10),
        )

    def forward(self, x):
        x = x.view(BATCH_SIZE, -1)
        x = self.model(x)
        return x

首先是x = x.view(BATCH_SIZE, -1)用来将图片展平成一维,因为我们此处使用的网络为全连接网络,输入输出都是一维的。

接下来我们定义了一个一层的全连接网络,nn.Linear(x, y)就代表全连接层,x代表输入端的个数,y代表输出端的个数。

同样的我们可以加深层数,如下:

self.model = nn.Sequential(
    nn.Linear(3072, 1024),
    nn.Linear(1024, 10),
)

这样,我们就已经构建起了一个简单的神经网络。

训练数据集

接下来,就是训练数据集的代码了。

# 用来设置执行设备,cpu/gpu
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

net = Net().to(device)
criterion = nn.CrossEntropyLoss().to(device)   # 定义损失函数,此处使用CrossEntropyLoss,其他算法请自行搜索
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)  # 定义优化器,此处使用SGD,其他算法请自行搜索

for epoch in range(10):   # 训练10轮
    for i, data in enumerate(train_loader, 0):
        inputs, labels = data    # input表示图片,label表示图片的分类
        inputs, labels = inputs.to(device), labels.to(device)

        optimizer.zero_grad()    # 重置网络梯度
        outputs = net(inputs)    # 用训练数据执行网络
        loss = criterion(outputs, labels)   # 计算结算偏差
        loss.backward()    # 反向传播计算梯度
        optimizer.step()   # 根据梯度调整系数

这段代码,就是执行网络训练的模板代码,先将梯度归0,再执行网络,再反向传播计算梯度,最后调整系数。至于这里面的各个算法具体的作用及原理,本文不进行详解。

就这样几行代码,训练就完成了。

测试数据集

最后,我们就拿训练好的模型,测试模型准确率。

correct = 0
total = 0
with torch.no_grad():   # 测试时不再需要计算梯度了,防止性能浪费
    for data in test_loader:
        images, labels = data
        images, labels = images.to(device), labels.to(device)
        outputs = net(images)   # 调用已训练好的模型识别测试图片
        _, predicted = torch.max(outputs.data, 1)  # 网络最终会输出10个分类对应的概率,取概率最高的
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

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

基本上也是模板代码,就不详解了。

小结

从以上代码中我们就可以基本了解一个神经网络的构建方式及其训练测试过程。当然里面还有很多概念没有提及,例如网络层除了全连接层之外,还可能会有卷积层、激活层等等。更多的可以去B站找找相关教程,搜索关键词——pytorch入门。

简单的工程应用

从一个软件工程师的角度,很显然,我们是没必要自己去真正去调试网络模型的,我们的目标是直接调用现成的网络模型用于生产就可以了。那pytorch也提供很多比较著名的网络模型,例如VGG、GoogLeNet、ResNet等。本文以ResNet(残差网络)为例,一行代码就可以了:

resnet = torchvision.models.resnet152(pretrained=True, progress=True)

那么接下来我们就来实际调用这个网络对图片进行识别,

import torch
import torchvision.models
from PIL import Image
from torchvision import transforms

# ResNet是基于ImageNet数据集的,此处imagenet_classes.txt存的是ImageNet的标签,见https://blog.csdn.net/LegenDavid/article/details/73335578
with open('./imagenet_classes.txt') as f:
    labels = [line.strip() for line in f.readlines()]

# 读取图片 & 预处理  (这里面的各种转换参数也是网上找的,没太细究)
image = Image.open('./img.png').convert('RGB')
preprocess = transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
image_preprocessed = preprocess(image)
batch_image_preprocessed = torch.unsqueeze(image_preprocessed, 0)

# 执行网络,并输出结果
resnet = torchvision.models.resnet152(pretrained=True, progress=True)
resnet.eval()
out = resnet(batch_image_preprocessed)
_, index = torch.max(out, 1)
print(labels[index])

可以看到,基本上真正的调用其实很简单,就是out = resnet(batch_image_preprocessed)。如果想应用到生产,有很多方式,最简单的就是外面包一层Django之类的web框架就行了。

参考

[1] PyTorch深度学习快速入门教程
[2] PyTorch – How to Load & Predict using Resnet Model