pytorch tensorboard_打造FashionMNIST CNN,PyTorch风格

2b444092fc88142c7f3badb823d1e44d.png

作者 | Lee

来源 | Medium

编辑 | 代码医生团队

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

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

64622c17531a215b9013038343c3c2ca.png

图片来自渐变

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 modulesimport torchimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optimfrom torch.utils.tensorboard import SummaryWriter # TensorBoard support # import torchvision module to handle image manipulationimport torchvisionimport torchvision.transforms as transforms # calculate train time, writing train data to files etc.import timeimport pandas as pdimport jsonfrom 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

44e38a2c055034de37e0b7614a2018f6.png

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

 

cad16b20a3ff1c361f9364231b7582bb.png

Fashion-MNIST数据集— 来自GitHub
# Use standard FashionMNIST datasettrain_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像素,因此实际上不需要非常复杂的网络。可以像这样构建一

44f4d0e1c9355fbdf022ed13e9bc057c.png

CNN拓扑

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

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

# Build the neural network, expand on top of nn.Moduleclass 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 expandableparams = 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 classesfrom collections  import OrderedDictfrom collections import namedtuplefrom itertools import product # Read in the hyper-parameters and return a Run namedtuple containing all the# combinations of hyper-parametersclass 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, jsonclass 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 classfor 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 filesm.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')。

91ac76835665e005ed06e5690848de0c.png

张量板

0143dfcf279e9eb25a3ce066415ff0b6.gif

图片来自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表现最佳,然后使用它来进行真正的训练。

 

0b5ebc818dff92b2c74048aaa4879704.png

97312b28a413c589ee9c6a47b139e2f4.png

25b1523094abcb26423c7b75f301cfeb.png

   

结论

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

ebf134cd3bfd66746c5b41049d914df3.png

推荐阅读

如何构建PyTorch项目

dffd820b3ced8a4821f1817b9e100160.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值