当前位置: 首页 > 科技观察

两个框架的故事:pytorch与tensorflow

时间:2023-03-18 15:57:51 科技观察

使用Pytorch1.x和Tensorflow2.x比较自动差异化和动态模型子类化方法>来源:作者数据科学社区是一个充满活力的协作空间。我们从彼此的出版物中学习,在论坛和在线渠道上讨论想法,并共享大量代码(和大量代码)。这种协作精神的自然副作用是很可能会遇到同事使用的不熟悉的工具。因为我们不是在真空中工作,所以熟悉给定主题领域的多种语言和库通常是有意义的,以便最有效地协作和学习。那么,许多数据科学家和机器学习工程师的工具箱中都有两个流行的机器学习框架也就不足为奇了:Tensorflow和Pytorch。这些框架-在Python中-有许多相似之处,但在有意义的方面也有所不同。这些差异,例如它们处理API、加载数据和支持专门领域的方式,可能会在两个框架之间交替出现,变得繁琐且效率低下。鉴于这两种工具的共性,这是一个问题。因此,本文旨在通过重点介绍创建和训练两个简单模型的基础知识来说明Pytorch和Tensorflow之间的区别。特别是,我们描述了如何使用来自Pytorch1.x的模块API和来自Tensorflow2.x的模块API来使用模型的动态子类化。我们将看到这些框架的自动差异如何提供非常简单的梯度下降实现。但首先是数据,因为我们专注于自微分/自导函数的核心(作为复习,自动提取函数的导数并在某些参数上应用梯度以便对这些参数使用梯度下降的能力)我们可以从最简单的模型入手,就是线性回归。我们可以使用Numpy库生成一些带有一些随机噪声的线性数据,然后在这个虚拟数据集上运行我们的模型。defgenerate_data(m=0.1,b=0.3,n=200):x=np.random.uniform(-10,10,n)noise=np.random.normal(0,0.15,n)y=(m*x+b)+noisereturnx.astype(np.float32),y.astype(np.float32)x,y=generate_data()plt.figure(figsize=(12,5))ax=plt.subplot(111)ax.scatter(x,y,c="b",label="samples")模型一旦我们有了数据,我们就可以在Tensorflow和Pytorch中从原始代码实现回归模型。为简单起见,我们一开始不使用任何层或激活器,只定义两个张量W和B,代表线性模型Y=Wx+B的权重和偏差。如您所见,除了一些差异API名称,两个模型的类定义几乎相同。最重要的区别是Pytorch需要一个显式参数对象来定义图形捕获的权重和偏差张量,而TensoRFlow能够自动捕获相同的参数。事实上,Pytorch的参数是Tensor的子类,在与模块API一起使用时具有一个特殊的属性:它们会自动将SELF添加到模块参数列表中,因此SECRES出现在parameter()迭代器中。这两个框架都从此类定义和执行方法中提取生成图形(__call__或forward)所需的一切,并且如下所示,计算实现bospropagation所需的梯度。Tensorflow动态模型classLinearRegressionKeras(tf.keras.Model):def__init__(self):super().__init__()self.w=tf.Variable(tf.random.uniform(shape=[1],-0.1,0.1))self.b=tf.Variable(tf.random.uniform(shape=[1],-0.1,0.1))def__call__(self,x):returnx*self.w+self.bPytorch动态模型类LinearRegressionPyTorch(torch.nn.模块):def__init__(self):super().__init__()self.w=torch.nn.Parameter(torch.Tensor(1,1).uniform_(-0.1,0.1))self.b=torch.nn.Parameter(torch.Tensor(1).uniform_(-0.1,0.1))defforward(self,x):returnx@self.w+self.b使用这些简单的Tensorflow和Bytorch模型构建构建训练循环、反向传播和优化器,下一步是实现损失函数,在本例中就是平方误差。然后我们可以实例化模型类并运行几个时期的训练循环。同样,由于我们专注于核心自动差异/自动差异功能,因此这里的目的是使用TensorFlow和特定于Pytorch的自动差异实现构建自定义训练循环。这些实现计算简单线性函数的梯度,并使用朴素的梯度下降优化器手动优化权重和偏置参数,本质上是最小化实际点与在每个点使用可微函数计算的预测之间的差异。计算损失。对于TensorFlow训练循环,我明确使用GradientTapeAPI来跟踪模型的前向执行和逐步损失计算。我使用GradientTape的渐变来优化权重和偏置参数。Pytorch提供了一种更“神奇”的自微分方法,可以隐式捕获对参数张量的任何操作,并提供用于优化权重和偏差参数的梯度,而无需调用其他API。一旦我有了权重和偏差梯度,在Pytorch和Tensorflow上实现自定义梯度下降方法就像从这些梯度中减去权重和偏差参数,乘以恒定学习率一样简单。请注意,由于Pytorch自动实现自动微分/自动推导,因此有必要在计算反向传播后显式调用no_gradapi。这指示Pytorch不要为权重和偏置参数的更新操作计算梯度。我们还需要显式释放先前在前向操作中自动计算的梯度,以阻止Pytorch跨批次和循环迭代自动累积梯度。Tensorflow训练循环defsquared_error(y_pred,y_true):returntf.reduce_mean(tf.square(y_pred-y_true))tf_model=LinearRegressionKeras()[w,b]=tf_model.trainable_variablesforepochinrange(epochs):withtf.GradientTape()ascale=predicttf_model(x)loss=squared_error(predictions,y)w_grad,b_grad=tape.gradient(loss,tf_model.trainable_variables)w.assign(w-w_grad*learning_rate)b.assign(b-b_grad*learning_rate)ifepoch%20==0:print(f"Epoch{epoch}:Loss{loss.numpy()}")Pytorch训练循环defsquared_error(y_pred,y_true):returntorch.mean(torch.square(y_pred-y_true))torch_model=LinearRegressionPyTorch()[w,b]=torch_model.parameters()forepochinrange(epochs):y_pred=torch_model(输入)loss=squared_error(y_pred,labels)loss.backward()withtorch.no_grad():w-=w.grad*learning_rateb-=b.grad*learning_ratew.grad.zero_()b.grad.zero_()ifepoch%20==0:print(f"Epoch{epoch}:Loss{loss.data}")Pytorch和Tensorflow模型现在重用可用层我已经展示了如何从原始代码中实现线性回归模型Pytorch和Tensorflow,我们可以看看如何使用TensorFlo的密集层和线性层具有现有层的TensorFlow和Pytorch动态模型你会注意到在模型初始化方法中,我们正在用TensorFlow和Pytorch中的线性层替换W和B参数的显式声明。两个层都实现线性回归,我们将指示它们使用单??个权重和偏差参数,而不是之前使用的显式W和B参数。密集和线性实现将在内部使用我们之前使用的相同张量声明(分别为tf.variable和nn.parameter)来分配这些张量并将它们与模型参数列表相关联。我们还将更新这些新模型类的调用/转发方法,以用密度/线性层代替手动线性回归计算。classLinearRegressionKeras(tf.keras.Model):def__init__(self):super().__init__()self.linear=tf.keras.layers.Dense(1,activation=None)#,input_shape=[1]defcall(self,x):returnsself.linear(x)classLinearRegressionPyTorch(torch.nn.Module):def__init__(self):super(LinearRegressionPyTorch,self).__init__()self.linear=torch.nn.Linear(1,1)defforward(self),x):returnsself.linear(x)使用可用的优化器和损失函数进行训练现在我们已经使用现有层重新实现了Tensorflow和Pytorch模型,我们可以专注于如何构建更优化的训练循环。我们将使用这些库提供的原生优化器和损失函数,而不是使用我们之前的Na?ve实现。我们将继续使用之前观察到的自动差分/自动导数功能,但这次使用标准梯度下降(SGD)优化实现以及标准损失函数。Tensorflowtrainingloop,easyfittingmethod在Tensorflow中,FIT()是一种非常强大的高级模型训练方法。它允许我们用指定超调参数的单一方法代替手动训练循环。在调用fit()之前,我们将使用Compile()方法编译模型类,然后通过梯度下降优化器和损失函数进行训练。您会注意到,在这种情况下,我们将尽可能多地重用TensorFlow库中的方法。特别是,我们会将标准随机梯度下降(SGD)优化器和标准平均绝对误差函数实现(MEAL_ABSOLUTE_ERROR)传递给编译方法。编译模型后,我们最终可以调用fit方法来全面训练我们的模型。我们将传递数据(x和y)、时期数和每个时期使用的批量大小。TensorFLOFTrainingLoopwithCustomLoopandSGDOptimizer在下面的代码片段中,我们将为我们的模型实现另一个自定义训练循环,这次尽可能重用Tensorflow库提供的损失函数和优化器。您会注意到我们之前的自定义Python损失函数被tf.losses.mse()方法所取代。我们没有使用梯度手动更新模型参数,而是初始化了TF.keras.optimizers.sgd()优化器。调用Optimizer.apply_gradient()并传递权重和偏差元组列表将使用梯度更新模型参数。tf_model_train_loop=LinearRegressionKeras()优化器=tf.keras.optimizers.SGD(learning_ratelearning_rate=learning_rate)forepochinrange(epochs*3):x_batch=tf.reshape(x,[200,1])withtf.GradientTape()astape:y_pred=tf_model_(x_batch)y_pred=tf.reshape(y_pred,[200])loss=tf.losses.mse(y_pred,y)grads=tape.gradient(loss,tf_model_train_loop.variables)optimizer.apply_gradients(grads_and_vars=zip(grads,tf_model_train_loop).variables))ifepoch%20==0:print(f"Epoch{epoch}:Loss{loss.numpy()}")Pytorch训练循环与自定义循环和SGD优化器vs.之前的Tensorflow片段同样,下面的代码代码片段通过重用Pytorch库提供的损失函数和优化器,为新模型实现Pytorch训练循环。您会注意到,我们将之前的自定义Python损失函数替换为NN.Mseloss()方法,并使用模型的学习参数列表初始化标准Optim.sgd()优化器。和之前一样,我们将指示Pytorch从损失反向传播(load.backward())中获取每个参数张量的关联梯度,最后,我们可以轻松更新新标准优化器,通过调用链接的所有参数更新为新的标准优化器optimizer.step()方法。Pytorch在张量和梯度之间建立自动关联的方式允许优化器检索张量和梯度,以使用配置的学习率更新它们。torch_model=LinearRegressionPyTorch()criterion=torch.nn.MSELoss(reduction='mean')optimizer=torch.optim.SGD(torch_model.parameters(),lr=learning_rate)forepochinrange(epochs*3):y_pred=torch_model(输入)loss=criterion(y_pred,labels)optimizer.zero_grad()loss.backward()optimizer.step()ifepoch%20==0:print(f"Epoch{epoch}:Loss{loss.data}")结果为正如我们所见,TensoRFlow和Pytorch自动差分和动态子类化API非常相似,尽管它们使用标准的SGD和MSE实现。当然,这两个模型也给了我们非常相似的结果。在下面的代码片段中,我们使用Tensorflow的training_variables和Pytorch的参数方法来访问模型的参数并绘制我们学习的线性函数的图形。[w_tf,b_tf]=tf_model_fit.trainable_variables[w2_tf,b2_tf]=tf_model_train_loop.trainable_variables[w_torch,b_torch]=torch_model.parameters()w_tf=tf.reshape(w_tf,[1])w2_tf=tf.reshape(w2_tf,1])withtorch.no_grad():plt.figure(figsize=(12,5))ax=plt.subplot(111)ax.scatter(x,y,c="b",label="samples")ax。plot(x,w_tf*x+b_tf,"r",linewidth=5.0,label="tensorflowfit")ax.plot(x,w2_tf*x+b2_tf,"y",linewidth=5.0,label="tensorflowtrainloop")ax.plot(x,w_torch*inputs+b_torch,"c",linewidth=5.0,label="pytorch")ax.legend()plt.xlabel("x1")plt.ylabel("y",旋转=0)结论Pytorch和新的Tensorflow2.x都支持动态图和自动差分核心函数来提取图中使用的所有参数的梯度。您可以使用任何损失函数和梯度后代优化器在Python中轻松实现训练循环。为了关注两个框架之间真正的核心差异,我们通过实现我们自己的简单MSE和Na?veSGD来简化上面的示例。但是,我强烈建议您在实现任何Na?ve代码之前重用这些库中可用的优化和专用代码。下表总结了上面示例代码中指出的所有差异。我希望它能在这两个框架之间切换时提供有用的参考。>来源:作者原文链接:https://medium.com/data-science-at-microsoft/a-tale-of-two-frameworks-pytorch-vs-tensorflow-f73a975e733d