前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >打造Fashion-MNIST CNN,PyTorch风格

打造Fashion-MNIST CNN,PyTorch风格

作者头像
代码医生工作室
发布2019-10-28 18:32:00
1.3K0
发布2019-10-28 18:32:00
举报
作者 | Lee 来源 | Medium

编辑 | 代码医生团队

关于技术框架,一个有趣的事情是,从一开始,似乎总是被各种选择。但是随着时间的推移,比赛将演变为只剩下两个强有力的竞争者。例如“ PC vs Mac”,“ iOS vs Android”,“ React.js vs Vue.js”等。现在,在机器学习中拥有“ PyTorch vs TensorFlow”。

由Google支持的TensorFlow无疑是这里的领先者。它于2015年作为开放源代码的机器学习框架发布,迅速获得了广泛的关注和认可,尤其是在生产准备和部署至关重要的行业中。PyTorch于2017年在Facebook上推出的很晚,但由于其动态的计算图和`` pythonic ''风格而很快赢得了从业者和研究人员的广泛喜爱。

图片来自渐变

The Gradient的最新研究表明,PyTorch在研究人员方面做得很好,而TensorFlow在行业界占主导地位:

在2019年,机器学习框架之战还有两个主要竞争者:PyTorch和TensorFlow。我的分析表明,研究人员正在放弃TensorFlow并大量涌向PyTorch。同时,在行业中,Tensorflow当前是首选平台,但长期以来可能并非如此。— 渐变

PyTorch 1.3的最新版本引入了PyTorch Mobile,量化和其他功能,它们都在正确的方向上缩小了差距。如果对神经网络基础有所了解,但想尝试使用PyTorch作为其他样式,请继续阅读。将尝试说明如何使用PyTorch从头开始为Fashion-MNIST数据集构建卷积神经网络分类器。如果没有强大的本地环境,则可以在Google Colab和Tensor Board上使用此处的代码。事不宜迟开始吧。可以在下面找到Google Colab Notebook和GitHub链接:

Co Google Colab笔记本

https://colab.research.google.com/drive/1YWzAjpAnLI23irBQtLvDTYT1A94uCloM

GitHub上

https://github.com/wayofnumbers/SideProjects/blob/master/PyTorch_Tutorial_Basic_v1.ipynb

Import

首先,导入必要的模块。

# import standard PyTorch modules
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.tensorboard import SummaryWriter # TensorBoard support
 
# import torchvision module to handle image manipulation
import torchvision
import torchvision.transforms as transforms
 
# calculate train time, writing train data to files etc.
import time
import pandas as pd
import json
from IPython.display import clear_output
 
torch.set_printoptions(linewidth=120)
torch.set_grad_enabled(True)     # On by default, leave it here for clarity

PyTorch模块非常简单。

Torch

torch是包含Tensor计算所需的所有内容的主要模块。可以单独使用Tensor计算来构建功能齐全的神经网络,但这不是本文的目的。将利用更强大和便捷torch.nn,torch.optim而torchvision类快速构建CNN。

torch.nn和torch.nn.functional

Alphacolor在Unsplash上拍摄的照片

该torch.nn模块提供了许多类和函数来构建神经网络。可以将其视为神经网络的基本构建块:模型,各种层,激活函数,参数类等。它可以像将一些LEGO集放在一起一样构建模型。

Torch优化

torch.optim 提供了SGD,ADAM等所有优化程序,因此无需从头开始编写。

Torch视觉

torchvision包含许多用于计算机视觉的流行数据集,模型架构和常见图像转换。我们从中获取Fashion MNIST数据集,并使用其变换。

SummaryWriter(张量板)

SummaryWriter使PyTorch可以为Tensor Board生成报告。将使用Tensor Board查看训练数据,比较结果并获得直觉。Tensor Board曾经是TensorFlow相对于PyTorch的最大优势,但是现在从v1.2开始,PyTorch正式支持它。

也引进了一些其他实用模块,如time,json,pandas,等。

数据集

torchvision已经具有Fashion MNIST数据集。如果不熟悉Fashion MNIST数据集:

Fashion-MNIST是Zalando文章图像的数据集-包含60,000个示例的训练集和10,000个示例的测试集。每个示例都是一个28x28灰度图像,与来自10个类别的标签相关联。我们打算Fashion-MNIST直接替代原始MNIST数据集,以对机器学习算法进行基准测试。它具有相同的图像大小以及训练和测试分割的结构。— 来自Github

https://github.com/zalandoresearch/fashion-mnist

Fashion-MNIST数据集— 来自GitHub

# Use standard FashionMNIST dataset
train_set = torchvision.datasets.FashionMNIST(
    root = './data/FashionMNIST',
    train = True,
    download = True,
    transform = transforms.Compose([
        transforms.ToTensor()                                 
    ])
)

这不需要太多解释。指定了根目录来存储数据集,获取训练数据,允许将其下载(如果本地计算机上不存在的话),然后应用transforms.ToTensor将图像转换为Tensor,以便可以在网络中直接使用它。数据集存储在dataset名为train_set.

网络

在PyTorch中建立实际的神经网络既有趣又容易。假设对卷积神经网络的工作原理有一些基本概念。如果没有,可以参考Deeplizard的以下视频:

Fashion MNIST的尺寸仅为28x28像素,因此实际上不需要非常复杂的网络。可以像这样构建一

CNN拓扑

有两个卷积层,每个都有5x5内核。在每个卷积层之后,都有一个最大步距为2的最大合并层。这能够从图像中提取必要的特征。然后,将张量展平并放入密集层中,通过多层感知器(MLP)来完成10类分类的任务。

现在已经了解了网络的结构,看看如何使用PyTorch来构建它:

# Build the neural network, expand on top of nn.Module
class Network(nn.Module):
  def __init__(self):
    super().__init__()
 
    # define layers
    self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
    self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)
 
    self.fc1 = nn.Linear(in_features=12*4*4, out_features=120)
    self.fc2 = nn.Linear(in_features=120, out_features=60)
    self.out = nn.Linear(in_features=60, out_features=10)
 
  # define forward function
  def forward(self, t):
    # conv 1
    t = self.conv1(t)
    t = F.relu(t)
    t = F.max_pool2d(t, kernel_size=2, stride=2)
 
    # conv 2
    t = self.conv2(t)
    t = F.relu(t)
    t = F.max_pool2d(t, kernel_size=2, stride=2)
 
    # fc1
    t = t.reshape(-1, 12*4*4)
    t = self.fc1(t)
    t = F.relu(t)
 
    # fc2
    t = self.fc2(t)
    t = F.relu(t)
 
    # output
    t = self.out(t)
    # don't need softmax here since we'll use cross-entropy as activation.
 
    return t

首先,PyTorch中的所有网络类都在基类上扩展nn.Module。它包含了所有基础知识:权重,偏差,正向方法,以及一些实用程序属性和方法,例如.parameters()以及.zero_grad()将使用的方法。

网络结构在__init__dunder函数中定义。

def __init__(self):
  super().__init__()
  # define layers
  self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
  self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)
  self.fc1 = nn.Linear(in_features=12*4*4, out_features=120)
  self.fc2 = nn.Linear(in_features=120, out_features=60)
  self.out = nn.Linear(in_features=60, out_features=10)

nn.Conv2d并且nn.Linear是内限定两个标准PyTorch层torch.nn模块。这些是不言而喻的。需要注意的一件事是,仅在此处定义了实际的图层。激活和最大池操作包含在下面说明的正向功能中。

# define forward function  
def forward(self, t):  
  # conv 1  
  t = self.conv1(t)  
  t = F.relu(t)  
  t = F.max_pool2d(t, kernel_size=2, stride=2)   
  # conv 2  
  t = self.conv2(t)   
  t = F.relu(t)  
  t = F.max_pool2d(t, kernel_size=2, stride=2)   
  # fc1   
  t = t.reshape(-1, 12*4*4)  
  t = self.fc1(t)  
  t = F.relu(t)   
  # fc2  
  t = self.fc2(t)  
  t = F.relu(t)  
  # output  
  t = self.out(t)  
  # don't need softmax here since we'll use cross-entropy as activation.   
  return t

一旦定义了层,就可以使用层本身来计算每个层的前向结果,再加上激活函数(ReLu)和最大池操作,可以轻松地编写上述网络的前向函数。请注意,在fc1(完全连接层1)上,使用了PyTorch的张量操作t.reshape来拉平张量,以便随后可以将其传递到密集层。另外,没有在输出层添加softmax激活函数,因为PyTorch的CrossEntropy函数将解决这个问题。

超参数

可以精选一组超参数和做一些实验和他们在一起。在这个例子中,想通过引入一些结构来做更多的事情。将构建一个系统来生成不同的超参数组合,并使用它们进行训练“运行”。每个“运行”使用一组超参数组合。将每次运行的训练数据/结果导出到Tensor Board,以便可以直接比较并查看哪个超参数集表现最佳。

将所有超参数存储在OrderedDict中:

# put all hyper params into a OrderedDict, easily expandable
params = OrderedDict(
    lr = [.01, .001],
    batch_size = [100, 1000],
    shuffle = [True, False]
)
epochs = 3

lr:学习率。想为模型尝试0.01和0.001。

batch_size:批次大小以加快训练过程。将使用100和1000。

shuffle:随机切换,是否在训练之前对批次进行随机混合。

一旦参数关闭。使用两个帮助程序类:RunBuilder和RunManager管理超参数和训练过程。

运行构建器

该类的主要目的RunBuilder是提供一个静态方法get_runs。它以OrderedDict(所有超参数都存储在其中)为参数,并生成一个命名元组Run,每个的元素run表示超参数的一种可能组合。此命名的元组稍后由训练循环使用。该代码很容易理解。

# import modules to build RunBuilder and RunManager helper classes
from collections  import OrderedDict
from collections import namedtuple
from itertools import product
 
# Read in the hyper-parameters and return a Run namedtuple containing all the
# combinations of hyper-parameters
class RunBuilder():
  @staticmethod
  def get_runs(params):
 
    Run = namedtuple('Run', params.keys())
 
    runs = []
    for v in product(*params.values()):
      runs.append(Run(*v))
    
    return runs

运行管理器

本RunManager 课程有四个主要目的。

  1. 计算并记录每个时期和运行的持续时间。
  2. 计算每个时期和跑步的训练损失和准确性。
  3. 记录每个时期的训练数据(例如,损失,准确性,权重,梯度,计算图等)并运行,然后将其导出到Tensor Board中进行进一步分析。
  4. 保存所有训练结果csv,json以备将来参考或提取API。

如您所见,它可以帮助处理物流,这对于成功训练模型也很重要。看一下代码。它有点长,所以请忍受:

# Helper class, help track loss, accuracy, epoch time, run time,
# hyper-parameters etc. Also record to TensorBoard and write into csv, json
class RunManager():
  def __init__(self):
 
    # tracking every epoch count, loss, accuracy, time
    self.epoch_count = 0
    self.epoch_loss = 0
    self.epoch_num_correct = 0
    self.epoch_start_time = None
 
    # tracking every run count, run data, hyper-params used, time
    self.run_params = None
    self.run_count = 0
    self.run_data = []
    self.run_start_time = None
 
    # record model, loader and TensorBoard
    self.network = None
    self.loader = None
    self.tb = None
 
  # record the count, hyper-param, model, loader of each run
  # record sample images and network graph to TensorBoard  
  def begin_run(self, run, network, loader):
 
    self.run_start_time = time.time()
 
    self.run_params = run
    self.run_count += 1
 
    self.network = network
    self.loader = loader
    self.tb = SummaryWriter(comment=f'-{run}')
 
    images, labels = next(iter(self.loader))
    grid = torchvision.utils.make_grid(images)
 
    self.tb.add_image('images', grid)
    self.tb.add_graph(self.network, images)
 
  # when run ends, close TensorBoard, zero epoch count
  def end_run(self):
    self.tb.close()
    self.epoch_count = 0
 
  # zero epoch count, loss, accuracy,
  def begin_epoch(self):
    self.epoch_start_time = time.time()
 
    self.epoch_count += 1
    self.epoch_loss = 0
    self.epoch_num_correct = 0
 
  #
  def end_epoch(self):
    # calculate epoch duration and run duration(accumulate)
    epoch_duration = time.time() - self.epoch_start_time
    run_duration = time.time() - self.run_start_time
 
    # record epoch loss and accuracy
    loss = self.epoch_loss / len(self.loader.dataset)
    accuracy = self.epoch_num_correct / len(self.loader.dataset)
 
    # Record epoch loss and accuracy to TensorBoard
    self.tb.add_scalar('Loss', loss, self.epoch_count)
    self.tb.add_scalar('Accuracy', accuracy, self.epoch_count)
 
    # Record params to TensorBoard
    for name, param in self.network.named_parameters():
      self.tb.add_histogram(name, param, self.epoch_count)
      self.tb.add_histogram(f'{name}.grad', param.grad, self.epoch_count)
    
    # Write into 'results' (OrderedDict) for all run related data
    results = OrderedDict()
    results["run"] = self.run_count
    results["epoch"] = self.epoch_count
    results["loss"] = loss
    results["accuracy"] = accuracy
    results["epoch duration"] = epoch_duration
    results["run duration"] = run_duration
 
    # Record hyper-params into 'results'
    for k,v in self.run_params._asdict().items(): results[k] = v
    self.run_data.append(results)
    df = pd.DataFrame.from_dict(self.run_data, orient = 'columns')
 
    # display epoch information and show progress
    clear_output(wait=True)
    display(df)
 
  # accumulate loss of batch into entire epoch loss
  def track_loss(self, loss):
    # multiply batch size so variety of batch sizes can be compared
    self.epoch_loss += loss.item() * self.loader.batch_size
 
  # accumulate number of corrects of batch into entire epoch num_correct
  def track_num_correct(self, preds, labels):
    self.epoch_num_correct += self._get_num_correct(preds, labels)
 
  @torch.no_grad()
  def _get_num_correct(self, preds, labels):
    return preds.argmax(dim=1).eq(labels).sum().item()
  
  # save end results of all runs into csv, json for further analysis
  def save(self, fileName):
 
    pd.DataFrame.from_dict(
        self.run_data,
        orient = 'columns',
    ).to_csv(f'{fileName}.csv')
 
    with open(f'{fileName}.json', 'w', encoding='utf-8') as f:
      json.dump(self.run_data, f, ensure_ascii=False, indent=4)

__init__:初始化必要的属性,例如计数,损失,正确预测的数量,开始时间等。

begin_run:记录运行的开始时间,以便在运行结束时可以计算出运行的持续时间。创建一个SummaryWriter对象以存储我们想要在运行期间导出到Tensor Board中的所有内容。将网络图和样本图像写入SummaryWriter对象。

end_run:运行完成后,关闭SummaryWriter对象,并将纪元计数重置为0(为下一次运行做好准备)。

begin_epoch:记录纪元开始时间,以便纪元结束时可以计算纪元持续时间。重置epoch_loss并epoch_num_correct。

end_epoch:大多数情况下都会发生此功能。当一个纪元结束时,将计算该纪元持续时间和运行持续时间(直到该纪元,除非最终的运行纪元,否则不是最终的运行持续时间)。将计算该时期的总损失和准确性,然后将记录的损失,准确性,权重/偏差,梯度导出到Tensor Board中。为了便于在Jupyter Notebook中进行跟踪,还创建了一个OrderedDict对象results,并将所有运行数据(损耗,准确性,运行计数,时期计数,运行持续时间,时期持续时间,所有超参数)放入其中。然后,将使用Pandas读取它并以整洁的表格格式显示它。

track_loss,track_num_correct,_get_num_correct:这些是实用功能以累积损耗,每批所以历元损失和准确性可以在以后计算的正确预测的数目。

save:保存所有运行数据(名单results OrderedDict所有实验对象)到csv和json作进一步的分析或API访问的格式。

这RunManager堂课有很多内容。恭喜到此为止!最困难的部分已经在身后。

训练

准备做一些训练!在RunBuilder 和RunManager的帮助下,训练过程变得轻而易举:

m = RunManager()
 
# get all runs from params using RunBuilder class
for run in RunBuilder.get_runs(params):
 
    # if params changes, following line of code should reflect the changes too
    network = Network()
    loader = torch.utils.data.DataLoader(train_set, batch_size = run.batch_size)
    optimizer = optim.Adam(network.parameters(), lr=run.lr)
 
    m.begin_run(run, network, loader)
    for epoch in range(epochs):
      
      m.begin_epoch()
      for batch in loader:
        
        images = batch[0]
        labels = batch[1]
        preds = network(images)
        loss = F.cross_entropy(preds, labels)
 
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
 
        m.track_loss(loss)
        m.track_num_correct(preds, labels)
 
      m.end_epoch()
    m.end_run()
 
# when all runs are done, save results to files
m.save('results')

首先,用于RunBuilder创建超参数的迭代器,然后循环遍历每种超参数组合以进行训练:

for run in RunBuilder.get_runs(params):

然后,network从Network上面定义的类创建对象。network = Network()。该network物体支撑着我们需要训练的所有重量/偏向。

还需要创建一个DataLoader 对象。这是一个保存训练/验证/测试数据集的PyTorch类,它将迭代该数据集,并以与batch_size指定数量相同的批次提供训练数据。

loader = torch.utils.data.DataLoader(train_set, batch_size = run.batch_size)

之后,将使用torch.optim类创建优化器。该optim课程将网络参数和学习率作为输入,将帮助逐步完成训练过程并更新梯度等。在这里,将使用Adam作为优化算法。

optimizer = optim.Adam(network.parameters(), lr=run.lr)

现在已经创建了网络,准备了数据加载器并选择了优化器。开始训练吧!

将循环遍历所有想要训练的纪元(此处为3),因此将所有内容包装在“纪元”循环中。还使用班级的begin_run方法RunManager来开始跟踪跑步训练数据。

m.begin_run(run, network, loader)    
for epoch in range(epochs):

对于每个时期,将遍历每批图像以进行训练。

m.begin_epoch()    
for batch in loader:              
  images = batch[0]      
  labels = batch[1]      
  preds = network(images)      
  loss = F.cross_entropy(preds, labels)
         
  optimizer.zero_grad()  
  loss.backward()      
  optimizer.step()
      
  m.track_loss(loss)      
  m.track_num_correct(preds, labels)

上面的代码是进行实际训练的地方。从批处理中读取图像和标签,使用network类进行正向传播(还记得forward上面的方法吗?)并获得预测。通过预测,可以使用cross_entropy函数计算该批次的损失。一旦计算出损失,就用重置梯度(否则PyTorch将积累不想要的梯度).zero_grad(),执行一种反向传播使用loss.backward()方法来计算权重/偏差的所有梯度。然后,使用上面定义的优化程序来更新权重/偏差。现在,针对当前批次更新了网络,将计算损失和正确预测的数量,并使用类的track_loss和track_num_correct方法进行累积/跟踪RunManager。

完成所有操作后,将使用将结果保存到文件中m.save('results')。

张量板

图片来自Tensorboard.org

Tensor Board是一个TensorFlow可视化工具,现在也PyTorch支持。已经采取了将所有内容导出到'./runs'文件夹的工作,Tensor Board将在其中查找要使用的记录。现在需要做的只是启动张量板并检查。由于在Google Colab上运行此模型,因此将使用一种称为的服务ngrok来代理和访问在Colab虚拟机上运行的Tensor Board。ngrok 首先安装:

!wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
!unzip ngrok-stable-linux-amd64.zip

然后,指定要从中运行Tensor Board的文件夹并启动Tensor Board Web界面(./runs为默认值):

LOG_DIR = './runs'
get_ipython().system_raw(
'tensorboard --logdir {} --host 0.0.0.0 --port 6006 &'
.format(LOG_DIR)
)

启动ngrok代理:

get_ipython().system_raw('./ngrok http 6006 &')

生成一个URL,以便可以从Jupyter Notebook中访问Tensor Board:

! curl -s http://localhost:4040/api/tunnels | python3 -c \
"import sys, json; print(json.load(sys.stdin)['tunnels'][0]['public_url'])"

如下所示,TensorBoard是一个非常方便的可视化工具,可深入了解训练,并可以极大地帮助调整超参数。可以轻松地找出哪个超参数comp表现最佳,然后使用它来进行真正的训练。

结论

如您所见,PyTorch作为一种机器学习框架是灵活,强大和富于表现力的。只需编写Python代码。由于本文的主要重点是展示如何使用PyTorch构建卷积神经网络并以结构化方式对其进行训练,因此我并未完成整个训练时期,并且准确性也不是最佳的。可以自己尝试一下,看看模型的性能如何。

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

本文分享自 相约机器人 微信公众号,前往查看

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

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

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