前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >明月深度学习实践002:关于模型训练你应该关注的内容

明月深度学习实践002:关于模型训练你应该关注的内容

作者头像
明月AI
发布2021-10-28 11:31:47
4860
发布2021-10-28 11:31:47
举报
文章被收录于专栏:野生AI架构师

前面我们已经建立了一个简单的LeNet模型,已经训练它了来做手写数字识别,基于mnist数据集上的效果还是不错的。今天接着写一些模型训练相关的内容。

说明:代码基于Pytorch。

0x01 GPU的几个常用函数


现在深度学习没有GPU,那基本是寸步难行。关于GPU在使用上,相比CPU确实坑多很多,有时莫名其妙就挂了。这里介绍几个常用的函数:

代码语言:javascript
复制
# 判断cuda是否可用
torch.cuda.is_available()

# 定义device
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 在数据准备好之后,应该统一转移到device
images = images.to(device)

# 查看显存的占用,除以2个1024,单位位MB
torch.cuda.max_memory_allocated()//(1024*1024)

# 释放显存占用
# empty_cache()来释放所有未使用的缓存的内存,以便其它 GPU 应用能够使用. 但是并不能释放 tensors 所占用的 GPU 显存
torch.cuda.empty_cache()

0x02 模型各层参数量查询


模型的参数会直接影响显存大小是否足够的问题,而显存往往又是相对昂贵的 计算资源,因此非常重要。Pytorch提供了查看模型参数的方法:

把这个对应到我们的模型代码:

代码语言:javascript
复制
        self.conv1 = nn.Sequential(     # input: 28*28*1
            # filter 大小 5×5,filter 深度(个数)为 6,padding为2,卷积步长s=1
            # 输出矩阵大小为 28×28×6
            nn.Conv2d(1, 6, 5, 1, 2),
            nn.ReLU(),
            # filter 大小 2×2(即 f=2),步长 s=2,no padding
            # 输出:14*14*6
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.conv2 = nn.Sequential(
            # filter 大小 5×5,filter 个数为 16,padding 为 0, 卷积步长 s=1
            # 输出矩阵大小为 10×10×16
            nn.Conv2d(6, 16, 5),
            nn.ReLU(),
            # 输出:5*5*16
            nn.MaxPool2d(2, 2)
        )
        # 全连接层
        self.fc1 = nn.Sequential(
            nn.Linear(16 * 5 * 5, 120),
            nn.ReLU()
        )
        # 全连接层
        self.fc2 = nn.Sequential(
            nn.Linear(120, 84),
            nn.ReLU()
        )
        # 全连接层,输出神经元数量为 10,代表 0~9 十个数字类别
        self.fc3 = nn.Linear(84, 10)

从这里我们能看得更清楚了,对于卷积层的参数量:

输出通道数 * 输入通道数 * 卷积核size + 输出通道数

其中输出通道数也等于卷积核数量,bias和输出通道数相等。

而对于全连接层的参数量计算,则更加简单:

输入神经元数量 * 输出神经元数量 + 输出神经元数量

其中bias等于输出神经元数量。

0x03 模型计算量


模型的计算量会直接模型的训练时长,在Pytorch上有一个thop的包可以进行计算(这个包需要安装: pip install thop),使用也非常简单:

这里flops是计算量,params是参数量。

0x04 记录模型评估指标


对应pytorch官方提供有一个可视化工具visdom,不过个人觉得这个东西不是太好用,如果只是记录指标的话,而如果使用tensorboardx,却只能自己使用,很难在团队之间分享结果,所以还是选择熟悉的mlflow,简单却好用。先初始化mlflow:

我们内部部署了一个独立的mlflow服务,只要往这里写数据,就能很方便的在团队之间进行分享。

代码语言:javascript
复制
import time
import torch.optim as optim

EPOCH = 10        # 遍历数据集次数
LR = 0.001        # 学习率

# 设置mlflow及记录参数
mlflow.set_experiment('LeNet测试实验')
mlflow.start_run()
mlflow.log_param('EPOCH', EPOCH)
mlflow.log_param('LR', LR)

# 定义是否使用GPU
print("cuda" if torch.cuda.is_available() else "cpu")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 定义损失函数loss function 和优化方式(采用SGD)
net = LeNet().to(device)
criterion = nn.CrossEntropyLoss()  # 交叉熵损失函数,通常用于多分类问题上
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9)

start = time.time()
for epoch in range(EPOCH):
    sum_loss = 0.0
    # 数据读取
    for i, data in enumerate(trainloader):
        inputs, labels = data
        inputs, labels = inputs.to(device), labels.to(device)

        # 梯度清零
        optimizer.zero_grad()

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

        # 每训练100个batch打印一次平均loss
        sum_loss += loss.item()
        if i % 100 == 99:
            print('[%d, %d] loss: %.03f' % (epoch + 1, i + 1, sum_loss / 100))
            sum_loss = 0.0

    # 每跑完一次epoch测试一下准确率
    with torch.no_grad():
        correct = 0
        total = 0
        for data in testloader:
            images, labels = data
            images, labels = images.to(device), labels.to(device)
            outputs = net(images)
            # 取得分最高的那个类
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum()
        print('第%d个epoch的识别准确率为:%.2f%%' % (epoch + 1, (100 * int(correct) / total)))
        mlflow.log_metric('Accuracy', 100 * int(correct) / total, step=epoch)

    # 保存模型
    torch.save(net.state_dict(), 'models/lenet/lenet_%03d.pth' % epoch)
    
# 记录运行时间与显存占用
# 这两个值对于非常重要,记录下来
mlflow.log_metric('gpu', torch.cuda.max_memory_allocated()//(1024*1024))
mlflow.log_metric('run_time', time.time()-start)
# 结束记录
mlflow.end_run()

# 释放显存占用
torch.cuda.empty_cache()

训练过程跟原来是一样的,只是增加了参数记录。当训练完成之后,会有一条记录:

最后准确率97.77%,显存占用6M,运行时间65.03秒。

点击进去,我们就能看到对应的准确率随着训练次数的增多而变化的趋势:

简单直观。

0x05 特征图


在训练完的时候,我们可能想知道一下模型卷积层输出的特征图是什么呢?我们可以对它们进行查看。

首先,我们随便加载一个图像,并执行 第一个卷积层:

我们可以看到,我们加载的这个图像应该是一个28*28数字8,在我们执行第一个卷积层之前,我们需要先讲这个图像的像素点归一化为0到1之间的值,除以255即可。Size中第一个元素1表示我们输入的是一个图像。

img_conv1是第一个卷积层输出的值,注意该值的shape,该值实际有6个通道,我们逐一将其展示出来看看:

代码语言:javascript
复制
print(torch.max(img_conv1), torch.min(img_conv1))
max_val = torch.max(img_conv1).to('cpu')
for i in range(img_conv1.shape[1]):
    _img = img_conv1[0][i]
    _img = _img.to('cpu').clone()
    _img = (_img/max_val)*255
    img_pil = transforms.ToPILImage()(_img)
    img_pil = img_pil.resize((112, 112))
    display(img_pil)

其输入大概如下:

事实上共有6个小图像,这里只是展示前2个,看形状长得跟原图差不多,应该是将原图的特征提取了出来,其他4个小图像也是类似。

这种特征图应该类似一种激活图的意思,就是说特征图中白色的像素表示该点的值越大,也该也意味着该特征越明显。

接着,我们把卷积层1的输出结果输入到卷积层2中:

这跟我们在模型的forward方法中的调用方式是非常接近的,输入的是6通道,输出的是16通道。而可视化也类似:

这里也是省略了剩下的14个小图像。这些特征激活图其实很难理解的。

0x06 关于LeNet后记


LeNet是最基础的模型之一,是我们很好的入门模型。这次我们讲了几个方面的内容:

  1. GPU的几个常用函数
  2. 模型参数量计算
  3. 模型计算量计算
  4. 模型训练指标记录
  5. 卷积层特征图查看

PS:后面希望自己能保持每周更新一篇的节奏吧,写文章的过程其实更多是自己总结学习的过程。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 野生AI架构师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档