乐趣区

关于人工智能:用PyTorch重新创建Keras-API

作者 |Bipin Krishnan P
编译 |VK
起源 |Towards Data Science

介绍

Francois Chollet 写的《Deep Learning with Python》一书让我进入了深度学习的世界。从那时起我就爱上了 Keras 的格调。

Keras 是我的第一个框架,而后是 Tensorflow,接着进入 PyTorch。

诚实说,在 Keras 的模型训练中,我很兴奋这个进度条,真是太棒了。

那么,为什么不尝试把 Keras 训练模型的教训带到 PyTorch 呢?

这个问题让我开始了工作,最初我用所有那些花哨的进度条重现了 Keras 的 Dense 层、卷积层和平坦层。

模型能够通过重叠一层到另一层来创立,并通过简略地调用 fit 办法进行训练,该办法相似于 Keras 的工作形式。

Keras 的工作形式如下:

# 一层一层叠起来
#采纳输出数据的形态
inputs = keras.Input(shape=(784,))
l1 = layers.Dense(64, activation="relu")(inputs)
l2 = layers.Dense(64, activation="relu")(l1)
outputs = layers.Dense(10)(l2)

model = keras.Model(inputs=inputs, outputs=outputs)

#输入模型摘要
model.summary()

#模型训练和评估
model.fit(x_train, y_train, epochs=2)
model.evaluate(x_test, y_test)

1. 导入所需的库

你可能不相熟库 pkbar,它用于显示相似 Keras 的进度条。

!pip install pkbar
import torch
from torch import nn
from torch import optim
from torch.autograd import Variable
from torchsummary import summary as summary_
import pkbar

import warnings
warnings.filterwarnings('ignore')

2. 输出层和 dense 层

输出层只是以数据的繁多实例的模式被传递到神经网络并返回它,对于全连贯的网络,它将相似于(1,784),对于卷积神经网络,它将是图像的尺寸(高度×宽度×通道)。

应用大写字母来命名 python 函数是违反规定的,然而咱们临时疏忽它(Keras 源代码的某些局部应用雷同的约定)。

def Input(shape):
  Input.shape = shape
  return Input.shape

def get_conv_output(shape, inputs):
  bs = 1
  data = Variable(torch.rand(bs, *shape))
  output_feat = inputs(data)

  return output_feat.size(1)

def same_pad(h_in, kernal, stride, dilation):
  return (stride*(h_in-1)-h_in+(dilation*(kernal-1))+1) / 2.0

Dense 类通过传递该层的输入神经元数量和激活函数来初始化。调用 Dense 层时,前一层作为输出传递。

当初咱们有了对于前一层的信息。如果前一层是输出层,则创立一个 PyTorch 线性层,其中输出层返回的形态和输入神经元的数量作为 Dense 类初始化期间的参数。

如果前一层是 Dense 层,咱们通过在 Dense 类中减少一个 PyTorch 线性层和一个激活层来扩大神经网络。

如果前一层是卷积层或平坦层,咱们将创立一个名为 get_conv_output() 的实用函数,通过卷积层和平坦层失去图像的输入形态。此维度是必须的,因为如果不向 in_features 参数传递值,则无奈在 PyTorch 中创立线性层。

函数的作用是将图像形态和卷积神经网络模型作为输出。而后,它创立一个与图像形态雷同的虚构张量,并将其传递给卷积网络(具备平坦层),并返回从中输入的数据的大小,该大小作为值传递给 PyTorch 线性层中的 in_features 参数。

class Dense(nn.Module):
  def __init__(self, outputs, activation):
    super().__init__()
    self.outputs = outputs
    self.activation = activation

  def __call__(self, inputs):
    self.inputs_size = 1
    
    if type(inputs) == tuple:
      for i in range(len(inputs)):
        self.inputs_size *= inputs[i]
      
      self.layers = nn.Sequential(nn.Linear(self.inputs_size, self.outputs),
        self.activation
    )

      return self.layers

    elif isinstance(inputs[-2], nn.Linear):
      self.inputs = inputs
      self.layers = list(self.inputs)
      self.layers.extend([nn.Linear(self.layers[-2].out_features, self.outputs), self.activation])

      self.layers = nn.Sequential(*self.layers)

      return self.layers

    else:
      self.inputs = inputs
      self.layers = list(self.inputs)
      self.layers.extend([nn.Linear(get_conv_output(Input.shape, self.inputs), self.outputs), self.activation])

      self.layers = nn.Sequential(*self.layers)

      return self.layers

3. 平坦层

为了创立一个平坦层,咱们将创立一个名为 FlattenedLayer 的自定义层类,它承受张量作为输出,并在前向流传期间返回张量的平坦版本。

咱们将创立另一个名为 flatten 的类,当调用这个层时,后面的层作为输出传递,而后 flatten 类通过在后面的层上增加咱们自定义创立的 FlattenedLayer 类来扩大网络。

因而,所有达到平坦层的数据都是应用咱们自定义创立的平坦层进行平坦的。

class FlattenedLayer(nn.Module):
  def __init__(self):
    super().__init__()
    pass

  def forward(self, input):
      self.inputs = input.view(input.size(0), -1)
      return self.inputs


class Flatten():
  def __init__(self):
    pass

  def __call__(self, inputs):
    self.inputs = inputs
    self.layers = list(self.inputs)
    self.layers.extend([FlattenedLayer()])
    self.layers = nn.Sequential(*self.layers)

    return self.layers

4. 卷积层

咱们将通过传入滤波器数量、内核大小、步长、填充、收缩和激活函数来初始化 Conv2d 层。

当初,当调用 Conv2d 层时,后面的层被传递给它,如果前一层是 Input layer,则是一个 Pytorch conv2d 层,其中提供了滤波器数量、内核大小、步长、填充,扩张和激活函数被创立,其中 in_channels 的值取自输出形态中的通道数。

如果前一层是卷积层,则通过增加一个 PyTorch Conv2d 层和激活函数来扩大前一层,激活函数的值取自前一层的 out_channels。

在填充的状况下,如果用户须要保留从该层传出的数据的维度,则能够将 padding 的值指定为“same”,而不是整数。

如果 padding 的值被指定为“same”,那么将应用一个名为 same_pad() 的实用函数来获取 padding 的值,以保留给定输出大小、内核大小、步长和收缩的维度。

能够应用后面探讨的 get_conv_output() 实用程序函数取得输出大小。

class Conv2d(nn.Module):
  def __init__(self, filters, kernel_size, strides, padding, dilation, activation):
    super().__init__()
    self.filters = filters
    self.kernel = kernel_size
    self.strides = strides
    self.padding = padding
    self.dilation = dilation
    self.activation = activation

  def __call__(self, inputs):

    if type(inputs) == tuple:
      self.inputs_size = inputs

      if self.padding == 'same':
        self.padding = int(same_pad(self.inputs_size[-2], self.kernel, self.strides, self.dilation))
      else:
        self.padding = self.padding

      self.layers = nn.Sequential(nn.Conv2d(self.inputs_size[-3],
                  self.filters, 
                  self.kernel, 
                  self.strides, 
                  self.padding,
                  self.dilation),
        self.activation
    )

      return self.layers

    else:
      if self.padding == 'same':
        self.padding = int(same_pad(get_conv_output(Input.shape, inputs), self.kernel, self.strides, self.dilation))
      else:
        self.padding = self.padding

      self.inputs = inputs
      self.layers = list(self.inputs)
      self.layers.extend([nn.Conv2d(self.layers[-2].out_channels, 
                    self.filters, 
                    self.kernel, 
                    self.strides, 
                    self.padding,
                    self.dilation),
             self.activation]
          )
      self.layers = nn.Sequential(*self.layers)

      return self.layers

5. 模型类

在构建了模型的体系结构之后,通过传入输出层和输入层来初始化模型类。然而我曾经给出了一个额定的参数,名为 device,它在 Keras 中不存在,这个参数承受值为 ’CPU’ 或 ’CUDA’,它将把整个模型挪动到指定的设施。

model 类的 parameters 办法用于返回要给 PyTorch 优化器的模型参数。

model 类有一个名为 compile 的办法,它承受训练模型所需的优化器和失落函数。模型类的摘要办法是借助 torch 的 summary 库显示所创立模型的摘要。

采纳拟合办法对模型进行训练,该办法以输出特色集、指标数据集和 epoch 数为参数。它显示由损失函数计算的损失和应用 pkbar 库的训练进度。

评估会计算验证数据集的损失和精度。

当应用 PyTorch 数据加载程序加载数据时,将应用 fit_generator、evaluate_generator 和 predict_generator。fit_generator 以训练集数据加载器和 epoch 作为参数。evaluate_generator 和 predict_generator 别离应用验证集数据加载器和测试数据加载器来掂量模型对未查看数据的执行状况。

class Model():
  def __init__(self, inputs, outputs, device):
    self.input_size = inputs
    self.device = device
    self.model = outputs.to(self.device)

  def parameters(self):
    return self.model.parameters()

  def compile(self, optimizer, loss):
    self.opt = optimizer
    self.criterion = loss

  def summary(self):
    summary_(self.model, self.input_size, device=self.device)
    print("Device Type:", self.device)

  def fit(self, data_x, data_y, epochs):
    self.model.train()

    for epoch in range(epochs):
      print("Epoch {}/{}".format(epoch+1, epochs))
      progress = pkbar.Kbar(target=len(data_x), width=25)
      
      for i, (data, target) in enumerate(zip(data_x, data_y)):
        self.opt.zero_grad()

        train_out = self.model(data.to(self.device))
        loss = self.criterion(train_out, target.to(self.device))
        loss.backward()

        self.opt.step()

        progress.update(i, values=[("loss:", loss.item())])

      progress.add(1)

  def evaluate(self, test_x, test_y):
    self.model.eval()
    correct, loss = 0.0, 0.0

    progress = pkbar.Kbar(target=len(test_x), width=25)

    for i, (data, target) in enumerate(zip(test_x, test_y)):
      out = self.model(data.to(self.device))
      loss += self.criterion(out, target.to(self.device))

      correct += ((torch.max(out, 1)[1]) == target.to(self.device)).sum()

      progress.update(i, values=[("loss", loss.item()/len(test_x)), ("acc", (correct/len(test_x)).item())])
    progress.add(1)


  def fit_generator(self, generator, epochs):
    self.model.train()

    for epoch in range(epochs):
      print("Epoch {}/{}".format(epoch+1, epochs))
      progress = pkbar.Kbar(target=len(generator), width=25)

      for i, (data, target) in enumerate(generator):
        self.opt.zero_grad()

        train_out = self.model(data.to(self.device))
        loss = self.criterion(train_out.squeeze(), target.to(self.device))
        loss.backward()

        self.opt.step()

        progress.update(i, values=[("loss:", loss.item())])

      progress.add(1)
      

  def evaluate_generator(self, generator):
    self.model.eval()
    correct, loss = 0.0, 0.0

    progress = pkbar.Kbar(target=len(generator), width=25)

    for i, (data, target) in enumerate(generator):
      out = self.model(data.to(self.device))
      loss += self.criterion(out.squeeze(), target.to(self.device))

      correct += (torch.max(out.squeeze(), 1)[1] == target.to(self.device)).sum()

      progress.update(i, values=[("test_acc", (correct/len(generator)).item()), ("test_loss", loss.item()/len(generator))])

    progress.add(1)

  def predict_generator(self, generator):
    self.model.train()
    out = []
    for i, (data, labels) in enumerate(generator):
      out.append(self.model(data.to(self.device)))

    return out

结尾

我用 Dense 层和卷积神经网络在 CIFAR100、CIFAR10 和 MNIST 数据集上测试了代码。它工作得很好,但还有很大的改良空间。

这是一个乏味的我的项目,我曾经工作了 3 - 4 天,它真的冲破了我用 PyTorch 编程的极限。

你能够在这里查看残缺的代码,并在下面提到的数据集上进行训练,或者你能够自在地调整代码以适宜你在 colab 中的爱好:https://colab.research.google…

原文链接:https://towardsdatascience.co…

欢送关注磐创 AI 博客站:
http://panchuang.net/

sklearn 机器学习中文官网文档:
http://sklearn123.com/

欢送关注磐创博客资源汇总站:
http://docs.panchuang.net/

退出移动版