作者|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 torchfrom torch import nnfrom torch import optimfrom torch.autograd import Variablefrom torchsummary import summary as summary_import pkbarimport warningswarnings.filterwarnings('ignore')
2.输出层和dense层
输出层只是以数据的繁多实例的模式被传递到神经网络并返回它,对于全连贯的网络,它将相似于(1,784),对于卷积神经网络,它将是图像的尺寸(高度×宽度×通道)。
应用大写字母来命名python函数是违反规定的,然而咱们临时疏忽它(Keras源代码的某些局部应用雷同的约定)。
def Input(shape): Input.shape = shape return Input.shapedef 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.inputsclass 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/