pytorch为什么要学习?拆解神经网络以了解Pytorch的内部功能
pytorch为什么要学习?拆解神经网络以了解Pytorch的内部功能> The values of all neurons in one layer.神经元的值取决于其输入,权重和偏差。 为了计算层中所有神经元的该值,我们计算输入矩阵与权重矩阵的点积,然后加上偏差矢量。 我们在写时简明扼要地表示:有时,您可能会感到欺骗,认为内部与发光的外部无法想象的很接近。 我希望您有幸打开了正确的玩具。 那些充满了错综复杂的东西,使它们值得打开。 也许您找到了一款具有未来感的直流电动机。 或者,也许是您在冰箱上尝试过的,外观奇特的扬声器,背面带有强力磁铁。 我确定当您发现导致控制器振动的原因时,它感觉恰到好处。我们将做完全一样的事情。 我们正在用数学和Pytorch拆除神经网络。 这将是值得的,而且我们的玩具甚至不会损坏。 也许您会感到沮丧。 这是可以理解的。 神经网络中有许多不同而复杂的部分。 这是压倒性的。 这是进入更明智状态的仪式。因此,为了帮助自己,我们需
带有示例和代码的简化数学运算可让您了解黑匣子内部的情况
> Photo by Florian Klauer on Unsplash
动机
小时候,您可能会在好奇心旺盛的时候拆卸玩具。 您可能被吸引到了发出声音的来源。 又或者是一个诱人的二极管发出的五颜六色的光呼唤您,将您的手移开了塑料开口。
有时,您可能会感到欺骗,认为内部与发光的外部无法想象的很接近。 我希望您有幸打开了正确的玩具。 那些充满了错综复杂的东西,使它们值得打开。 也许您找到了一款具有未来感的直流电动机。 或者,也许是您在冰箱上尝试过的,外观奇特的扬声器,背面带有强力磁铁。 我确定当您发现导致控制器振动的原因时,它感觉恰到好处。
我们将做完全一样的事情。 我们正在用数学和Pytorch拆除神经网络。 这将是值得的,而且我们的玩具甚至不会损坏。 也许您会感到沮丧。 这是可以理解的。 神经网络中有许多不同而复杂的部分。 这是压倒性的。 这是进入更明智状态的仪式。
因此,为了帮助自己,我们需要参考,以某种北极星来确保我们走上正确的道路。 Pytorch的预建功能将是我们的Polaris。 他们会告诉我们我们必须获得的输出。 找到我们将导致我们获得正确输出的逻辑将落在我们身上。 如果差异听起来像您曾经认识的被遗忘的陌生人,那么就不要烦恼! 我们将再次进行介绍,这将是非常愉快的。 希望您会喜欢。
线性度神经元的值取决于其输入,权重和偏差。 为了计算层中所有神经元的该值,我们计算输入矩阵与权重矩阵的点积,然后加上偏差矢量。 我们在写时简明扼要地表示:
> The values of all neurons in one layer.
然而,数学方程式的简洁性是通过内部构造的抽象实现的。 我们为简洁而付出的代价是使所涉及的步骤更加难以理解和直观化。 为了能够对诸如神经网络之类的复杂结构进行编码和调试,我们需要深刻的理解和清晰的思维可视化。 为此,我们赞成冗长:
> The value of one neuron with three inputs three weights and a bias.
现在,该方程式基于特定情况施加的约束:一个神经元,三个输入,三个权重和一个偏差。 我们已经从抽象转向更具体的东西,可以轻松实现:
import torch
x = torch.tensor([0.9 0.5 0.3])
w = torch.tensor([0.2 0.1 0.4])
b = torch.tensor([0.1])
z_v = x[0]*w[0] x[1]*w[1] x[2]*w[2] b # Verbose z
z = x @ w b # Concise z
print(f"Verbose z: {z_v} \nConcise z: {z}")
'''
Out:
Verbose z: tensor([0.4500])
Concise z: tensor([0.4500])
为了计算z,我们已经从输入层移到了神经元的下一层。 当神经网络一直前进到其各个层并获取知识时,它需要知道如何向后调整其先前的层。 我们可以通过导数来实现知识的向后传播。 简而言之,如果我们根据z的每个参数(权重和偏差)对z进行区分,则可以获得输入层x的值。
如果您忘记了如何区分,请放心:不会告诉您重新学习整个微积分。 我们将在需要时回顾差异化规则。 z关于参数的偏导数告诉您将参数视为变量,并将所有其他参数视为常量。 变量的导数等于其系数。 常数的导数等于零:
> w0 is the variable all else is a constant. The coefficient of w0 is x0.
同样,您可以区分w1,w2和b(其中b的不可见系数为1)。 您会发现z的每个偏导数都等于参数的系数。 考虑到这一点,我们可以使用Pytorch Autograd评估数学的正确性。
import torch
x = torch.tensor([0.9 0.5 0.3])
w = torch.tensor([0.2 0.1 0.4] requires_grad=True)
b = torch.tensor([0.1] requires_grad=True)
# requires_grad=True tells Pytorch we are differentiating w.r.t the weights and bias
z = x @ w b
z.backward() # differentiates z and stores the derivatives in w.grad and b.grad
print(f"Partial derivatives: {w.grad} {b.grad}")
'''
Out:
Partial derivatives: tensor([0.9000 0.5000 0.3000]) tensor([1.])
非线性度
我们用激活函数介绍非线性。 这使神经网络成为通用函数逼近器。 激活有多种类型,每种激活实现不同的目的并产生不同的效果。 我们将研究ReLU,Sigmoid和Softmax的公式和区分。
ReLU整流线性单位函数将神经元的值与零进行比较,并输出最大值。 我们可以认为ReLU将所有非阳性神经元标记为同等无效。
> All non-negative values stay the same while negative values are replaced by zero.
为了实现自己的ReLU,我们可以将z与0进行比较,然后输出更大的一个。 但是Torch软件包中提供的钳位方法已经可以为我们完成此任务。 在Numpy中,等效功能称为clip。 以下代码在使用Pytorch的relu评估其输出之前,实现了基于钳位的ReLU。
import torch
import torch.nn.functional as F
#----------- Implementing the math -----------#
def relu(z):
return torch.clamp(z 0 None) # None specifies that we don't require an upper-bound
z = torch.tensor([[-0.2] [0.] [0.6]]) # Three neurons with different values
relu = relu(z)
#----------- Using Pytorch -----------#
torch_relu = F.relu(z)
#----------- Comparing outputs -----------#
print(f"Pytorch ReLU: \n{torch_relu} \nOur ReLU: \n{relu}")
'''
Out:
Pytorch ReLU:
tensor([[0.0000] [0.0000] [0.6000]])
Our ReLU:
tensor([[0.0000] [0.0000] [0.6000]])
'''
ReLU的区别很简单:
> ReLU' is either 1 or 0 depending on z.
· 对于所有正z,ReLU的输出为z。 因此,微分是z的系数,等于1。
· 对于所有非正z,ReLU的输出等于零。 因此,微分也等于零。
让我们将我们的理解转化为Python代码。 在将其与Pytorch的ReLU的自动区分进行比较之前,我们将实现自己的ReLU(z)。
import torch
import torch.nn.functional as F
#----------- Implementing the math -----------#
def relu_prime(z):
return torch.where(z>0 torch.tensor(1.) torch.tensor(0.))
z = torch.tensor([[-0.2] [0.6]] requires_grad=True)
relu_p = relu_prime(z)
#----------- Using Pytorch autograd -----------#
torch_relu = F.relu(z)
torch_relu.backward(torch.tensor([[1.] [1.]]))
#----------- Comparing outputs -----------#
print(f"Pytorch ReLU': \n{z.grad} \nOur ReLU': \n{relu_p}")
'''
Out:
Pytorch ReLU':
tensor([[0.] [1.]])
Our ReLU':
tensor([[0.] [1.]])
'''
我们为什么要给张量的张量backward()? 向后()默认情况下是在单个标量上调用的情况,并使用默认参数torch.tensor(1。)。以前在我们调用z.backward()时就是这种情况。 由于torch_relu不是单个标量,因此我们需要显式提供形状与torch_relu相等的张量。
SigmoidS型激活函数产生将z从ℝ映射到[0 1]范围的效果。 在执行二进制分类时,通常将属于目标类的实例标记为1,将所有其他实例的标记为0。我们将Sigmoid的输出解释为实例属于目标类的概率。
> The sigmoid activation function produces the effect of mapping z from ℝ to the range [0 1].
测验:神经网络的任务是执行二进制分类。 该网络的输出层由单个神经元组成,该神经元的S型激活量等于0.1。 在以下解释中,哪一个是正确的?
· 实例属于目标类别1的可能性为0.1。
· 实例属于类0的概率为0.1。
· 实例属于类0的概率为0.9。
解决方案:只有1和3是正确的。 重要的是要理解,具有某些输出p的S型神经元会隐式地为非目标类别提供1-p的输出。 同样重要的是要记住,p是与目标类别(通常标记为1)相关联的概率,而1-p是与非目标类别(通常标记为0)相关联的概率。
观察:p和(1-p)的和等于1。这在当前阶段似乎太明显了,但是在我们讨论Softmax时记住这一点对我们很有用。
再次,我们用Python转换数学运算,然后使用Sigmoid的Pytorch实现检查结果:
import torch
#----------- Implementing the math -----------#
def sigmoid(z):
return 1 / (1 torch.exp(-z))
z = torch.tensor([[2.] [-3.]]) # Two neurons with different values
sig = sigmoid(z)
#----------- Using Pytorch -----------#
torch_sig = torch.sigmoid(z)
#----------- Comparing outputs -----------#
print(f"Pytorch Sigmoid: \n{torch_sig} \nOur Sigmoid: \n{sig}")
'''
Out:
Pytorch Sigmoid:
tensor([[0.8808] [0.0474]])
Our Sigmoid:
tensor([[0.8808] [0.0474]])
'''
> Sigmoid differentiation.
Sigmoid的分化有些优美。 但是,它确实走了一条曲折的道路才能达到它的优雅。 一旦我们回顾了一些差异化规则,我们将拥有在蜿蜒曲折的道路上徘徊的所有必要条件。
> Detailed differentiation of sigmoid.
了解了如何区分Sigmoid之后,我们现在可以实施数学运算,并使用Pytorch的Autograd对其进行评估。
import torch
#----------- Implementing the math -----------#
def sigmoid_prime(z):
return sigmoid(z) * (1 - sigmoid(z))
z = torch.tensor([[2.] [-3.]] requires_grad=True)
sig_p = sigmoid_prime(z)
#----------- Using Pytorch autograd -----------#
torch_sig.backward(torch.tensor([[1.] [1.]]))
#----------- Comparing outputs -----------#
print(f"Pytorch Sigmoid': \n{z.grad} \nOur Sigmoid': \n{sig_p}")
'''
Out:
Pytorch Sigmoid':
tensor([[0.1050] [0.0452]])
Our Sigmoid':
tensor([[0.1050] [0.0452]])
'''
如今,ReLU已被广泛用作Sigmoid的替代物。 但是,Sigmoid仍在徘徊,以其更笼统的形式:Softmax隐藏。
softmax我们认为Sigmoid用于二进制分类,而softmax用于多分类。 这种关联虽然正确,但却使我们许多人误以为Sigmoid和softmax是两个不同的功能。 当我们看一下S形和softmax的方程时,似乎并没有明显的联系,这一点凸显了这一点。
> A softmax activated neuron is the exponential of its value dived by the sum of the exponentials of
公式的抽象再一次使它变得一目了然,一目了然。 一个例子将使其更加具体。 我们以两个输出神经元为例,第一个(z0)输出实例属于标记为0的类的概率,第二个(z1)输出实例属于标记为1的类的概率。 z0将目标类标记为0,对于z1,目标类标记为1。要使用softmax激活z0和z1,我们计算:
> Softmax is applied to each neuron in the output layer. In addition to mapping all neurons from ℝ
现在,我们可以补救Sigmoid和softmax之间似乎缺乏明显联系的情况。 我们将通过简单地重写sigmoid来做到这一点:
> Another way of writing sigmoid showing that it's actually softmax with two classes.
看到第一个提到的Sigmoid比看到第二个更常见。 这是因为后面的版本在计算上更昂贵。 但是,它的优势仍然在于帮助我们了解softmax。
在输出层中只有两个神经元的情况下,并且考虑到softmax使所有输出神经元的总和为1的事实,我们始终知道Softmax(z0)将等于1-Softmax(z1)。 因此,对于二进制分类,将z0等于0并仅使用sigmoid计算z1的激活是有意义的。
以下代码实现softmax并使用三个输出神经元的示例对其进行测试。 然后将我们的结果与Pytorch的softmax结果进行比较。
import torch
import torch.nn.functional as F
#----------- Implementing the math -----------#
def softmax(z):
return z.exp() / z.exp().sum(axis=1 keepdim=True)
# keepdim=True tells sum() that we want its output to have the same dimension as z
zs = torch.tensor([[2. 3. 1.]]) # Three output neurons
sm = softmax(zs)
#----------- Using Pytorch -----------#
torch_sm = F.softmax(zs dim=1)
#----------- Comparing outputs -----------#
print(f"Pytorch Softmax: \n{torch_sm} \nOur Softmax: \n{sm}")
'''
Out:
Pytorch Softmax:
tensor([[0.2447 0.6652 0.0900]])
Our Softmax:
tensor([[0.2447 0.6652 0.0900]])
'''
我们针对每个神经元区分softmax激活。 与具有两个神经元的输出层的示例相同,我们得到了四个softmax差异:
> The Jacobian matrix of softmax.
无论输出神经元的数量如何,softmax分化只有两个公式。 当我们区分神经元相对于自身的softmax时,将应用第一个公式(雅可比行列的左上角和右下角微分)。 当我们相对于某些其他神经元区分神经元的softmax时,将应用第二个公式(雅可比行列式的右上和左下区分)。
要了解softmax区分所涉及的步骤,我们需要回顾另一条区分规则:
> The division rule.
以下区别包含详细步骤。 尽管它们看上去很密集,但看上去似乎很吓人,但我向您保证,它们比它们看起来容易得多,我鼓励您在纸上重做它们。
> Detailed partial differentiations of softmax.
softmax区分的实现要求我们遍历神经元列表并针对每个神经元进行区分。 因此,涉及两个循环。 请记住,这些实现的目的不是高效,而是显式转换数学并获得通过Pytorch的内置方法获得的相同结果。
import torch
import torch.nn.functional as F
#----------- Implementing the math -----------#
def softmax(z):
return z.exp() / z.exp().sum(axis=1 keepdim=True)
def softmax_prime(z):
sm = softmax(z).squeeze()
sm_size = sm.shape[0]
sm_ps = []
for i sm_i in enumerate(sm):
for j sm_j in enumerate(sm):
# First case: i and j are equal:
if(i==j):
# Differentiating the softmax of a neuron w.r.t to itself
sm_p = sm_i * (1 - sm_i)
sm_ps.append(sm_p)
# Second case: i and j are not equal:
else:
# Differentiating the softmax of a neuron w.r.t to another neuron
sm_p = -sm_i * sm_j
sm_ps.append(sm_p)
sm_ps = torch.tensor(sm_ps).view(sm_size sm_size)
return sm_ps
z = torch.tensor([[4. 2.]] requires_grad=True)
sm_p = softmax_prime(z)
#----------- Using Pytorch autograd -----------#
torch_sm = F.softmax(z dim=1)
# to extract the first row in the jacobian matrix use [[1. 0]]
# retain_graph=True because we re-use backward() for the second row
torch_sm.backward(torch.tensor([[1. 0.]]) retain_graph=True)
r1 = z.grad
z.grad = torch.zeros_like(z)
# to extract the second row in the jacobian matrix use [[0. 1.]]
torch_sm.backward(torch.tensor([[0. 1.]]))
r2 = z.grad
torch_sm_p = torch.cat((r1 r2))
#----------- Comparing outputs -----------#
print(f"Pytorch Softmax': \n{torch_sm_p} \nOur Softmax': \n{sm_p}")
'''
Out:
Pytorch Softmax':
tensor([[ 0.1050 -0.1050]
[-0.1050 0.1050]])
Our Softmax':
tensor([[ 0.1050 -0.1050]
[-0.1050 0.1050]])
'''
交叉熵损失
在神经网络所涉及的操作序列中,softmax通常后面是交叉熵损失。 实际上,这两个功能是如此紧密地联系在一起,以至于在Pytorch中,方法cross_entropy将两个功能合并为一个。
我记得当我看到交叉熵损失的公式时给我的第一印象。 它接近于欣赏象形文字。 解密后,希望您对简单的想法有时具有最复杂的表示方式感到敬畏。
> Cross-entropy loss function.
计算交叉熵损失所涉及的变量是p,y,m和K。i和k都用作分别从1迭代到m和K的计数器。
· Z:是一个数组,其中每一行代表一个实例的输出神经元。 m:是实例数。
· K:是班数。
· p:是实例i属于类别k的神经网络的概率。 这与从softmax计算得出的概率相同。
· y:是实例i的标签。 根据y是否属于类别k,它可以是1或0。
· log:是自然对数。
假设我们正在执行一个多类别分类任务,其中可能的类别数量是三(K = 3)。 每个实例只能属于一个类。 因此,将每个实例分配给带有两个0和一个1的标签向量。 例如y = [0 0 1]表示y的实例属于类2。类似地,y = [1 0 0]表示y的实例属于类0。1的索引引用 实例所属的类。 我们说标签是一键编码的。
现在,我们来看两个实例(m = 2)。 我们计算它们的z值,发现:Z = [[0.1,0.4,0.2],[0.3,0.9,0.6]]。 然后,我们计算它们的softmax概率并找到:激活= [[0.29,0.39,0.32],[0.24,0.44,0.32]]。 我们知道第一个实例属于类2,第二个实例属于类0,因为:y = [[0 0 1],[1 0 0]]。
要计算交叉熵:
· 我们获得softmax激活的对数:log(activations)= [[-1.24,-0.94,-1.14],[-1.43,-0.83,-1.13]]。
· 我们乘以-1得到负对数:-log(activations)= [[1.24,0.94,1.14],[1.43,0.83,1.13]]。
· -log(activations)乘以y得到:[[0.,0.,1.14],[1.43,0.,0.]]。
· 所有类别的总和为:[[0. 0. 1.14],[1.43 0. 0。]] = [[1.14],[1.43]]
· 所有实例的总和为:[1.14 1.43] = [2.57]
· 除以实例数得出:[2.57 / 2] = [1.285]
观察结果:
· 步骤3和4等效于简单地检索目标类的否定日志。
· 步骤5和6等效于计算平均值。
· 当神经网络以0.32的概率预测该实例属于目标类别时,损失等于1.14。
· 当神经网络以0.24的概率预测该实例属于目标类别时,损失等于1.43。
· 我们可以看到,在两种情况下,网络都未能为正确的类别提供最高的概率。 但是与第一个实例相比,网络对第二个实例不属于正确的类更有信心。 因此,它遭受了1.43的更高损失。
在实现交叉熵时,我们将上述步骤和观察结果结合在一起。 与往常一样,在比较两个输出之前,我们还将介绍Pytorch等效方法。
import torch
import torch.nn.functional as F
#----------- Implementing the math -----------#
def cross_entropy(activations labels):
return - torch.log(activations[range(labels.shape[0]) labels]).mean()
zs = torch.tensor([[0.1 0.4 0.2] [0.3 0.9 0.6]]) # The values of 3 output neurons for 2 instances
activations = softmax(zs) # = [[0.2894 0.3907 0.3199] [0.2397 0.4368 0.3236]]
y = torch.tensor([2 0]) # equivalent to [[0 0 1] [1 0 0]]
ce = cross_entropy(activations y)
#----------- Using Pytorch autograd -----------#
torch_ce = F.cross_entropy(zs y)
#----------- Comparing outputs -----------#
print(f"Pytorch cross-entropy: {torch_ce} \nOur cross-entropy: {ce}")
'''
Out:
Pytorch cross-entropy: 1.28411
Our cross-entropy: 1.28411
'''
注意:我们只存储索引1的索引,而不是存储标签的一键编码。例如,前一个y为[2 0]。 注意,在索引0处y的值为2,在索引1处y的值为0。使用y的索引及其值,我们可以直接检索目标类的负对数。 这是通过访问第0行第2列和第1行第0列的-log(activations)来完成的。这使我们能够避免在步骤3和4中浪费零的乘法和加法运算。此技巧称为整数数组索引,并且 杰里米·霍华德(Jeremy Howard)在他的《从基金会进行的深度学习》讲座9中的34:57中进行了解释
如果将遍历神经网络的各个层看作是获取某种知识的过程,那么这里就是可以找到该知识的地方。 使用损失函数的微分可以告知神经网络每个实例有多少错误。 将这个错误向后看,神经网络可以自我调整。
> Cross-entropy differentiation.
在回忆起几个微分规则之后,我们经历了交叉熵的微分步骤:
> Recall these two differentiation rules. Also recall that ln is the same as log based e. The base e
> Cross-entropy differentiation steps.
我们现在还无法使用Pytorch Autograd的输出评估以下实现。 原因可以归结为Pytorch的cross_entropy结合了softmax和cross-entropy。 因此,向后使用也将涉及链规则中softmax的区分。 我们将在下一部分"反向传播"中讨论和实现这一点。 目前,这是我们的交叉熵实现":
import torch
zs = torch.tensor([[0.1 0.4 0.2] [0.3 0.9 0.6]]) # The values of 3 output neurons for 2 instances
activations = softmax(zs) # = [[0.2894 0.3907 0.3199] [0.2397 0.4368 0.3236]]
y = torch.tensor([2 0]) # equivalent to [[0 0 1] [1 0 0]]
#----------- Implementing the math -----------#
def crossentropy_prime(activations labels):
n = labels.shape[0]
activs = torch.zeros_like(activations)
activs[range(n) labels] = -1 / activations[range(n) labels] # integer array indexing
return activs
c_p = crossentropy_prime(activations y)
#----------- Printing Output -----------#
print(f"Cross-entropy differentiation: \n{c_p}\n")
'''
Out:
Cross-entropy differentiation:
tensor([[ 0.0000 0.0000 -3.1262]
[-4.1720 0.0000 0.0000]])
'''
反向传播
对于我们讨论的每个函数,我们都在神经网络的各个层中向前迈出了一步,并且还利用函数的微分向后迈出了等效的一步。 由于神经网络一直向前移动,然后逐步向后移动,因此我们需要讨论如何连接我们的功能。
前向一直向前,具有一个隐藏层的神经网络首先将输入馈送到线性函数,然后将其输出馈送到非线性函数,然后将其输出馈送到损耗函数。 以下是一个实例x的示例,该实例具有x,其对应的标记y,三个线性神经元z,每个神经元使用其三个权重w和一个偏差b来计算,然后是softmax激活层和交叉熵损失。
y = torch.tensor([2])
x = torch.tensor([[0.9 0.5 0.3]])
w = torch.tensor([[0.2 0.1 0.4] [0.5 0.6 0.1] [0.1 0.7 0.2]] requires_grad=True)
b = torch.tensor([[0.1 0.2 0.1]] requires_grad=True)
#----------- Using our functions -----------#
z = x @ w.T b # = [[0.4500 0.9800 0.6000]]
sm = softmax(z) # = [[0.2590 0.4401 0.3009]]
ce = cross_entropy(sm y) # = 1.2009
#----------- Equivalent Pytorch -----------#
后向
一直往回走,相同的神经网络从获取给损失函数的相同输入开始,然后将其馈送到该损失函数的导数。 损失函数的导数的输出是误差,我们称之为获得的知识。 为了调整其参数,神经网络必须将此误差再向后退至非线性层,然后再向非线性层再退一步。
向后的下一步并不像将误差馈入非线性函数的导数那样简单。 我们需要使用链式规则(我们之前曾在Sigmoid的微分中回想过),并且还需要注意对每个导数的输入。
前馈和反向传播的关键规则:
· 函数及其派生类采用相同的输入。
· 函数将其输出转发为下一个函数的输入。
· 导数将其输出向后发送,以乘以前导数的输出。
#----------- Using our differentiations -----------#
ce_p = crossentropy_prime(sm y) # = [[ 0.0000 0.0000 -3.3230]]
sm_p = softmax_prime(z) # = [[ 0.1919 -0.1140 -0.0779]
# [-0.1140 0.2464 -0.1324]
# [-0.0779 -0.1324 0.2104]])
z_p_w = torch.stack(([x]*3)).squeeze() # Recall: z' w.r.t the weights is equal to x
z_p_b = torch.ones_like(b) # Recall: z' w.r.t the biases is equal to 1
# Backwards from cross-entropy to softmax
ce_sm = (ce_p @ sm_p.T)
# Backwards from softmax to z
our_w_grad = ce_sm.T * z_p_w
our_b_grad = ce_sm * z_p_b
#----------- Using Pytorch Autograd -----------#
t_ce.backward()
t_w_grad = w.grad
t_b_grad = b.grad
#----------- Comparing Outputs -----------#
print(f"Pytorch w_grad: \n{t_w_grad} \nPytorch b_grad: \n{t_b_grad}")
print(f"Math w_grad: \n{our_w_grad} \nMath b_grad: \n{our_b_grad}")
'''
Out:
Pytorch w_grad:
tensor([[ 0.2331 0.1295 0.0777]
[ 0.3960 0.2200 0.1320]
[-0.6292 -0.3495 -0.2097]])
Pytorch b_grad:
tensor([[ 0.2590 0.4401 -0.6991]])
Math w_grad:
tensor([[ 0.2331 0.1295 0.0777]
[ 0.3960 0.2200 0.1320]
[-0.6292 -0.3495 -0.2097]])
Math b_grad:
tensor([[ 0.2590 0.4401 -0.6991]])
'''
结论
我的印象是,许多具有不同学科背景的人对机器学习充满好奇和热情。 不幸的是,有一种合理的趋势,那就是在设法避开令人生畏的数学的同时获取专有技术。 我认为这很不幸,因为我相信许多人实际上渴望加深理解。 如果他们能找到更多的资源来吸引他们来自不同背景的事实,并且可能需要在这里和那里稍加提醒和一点鼓励。
本文包含了我对编写易于阅读的数学的尝试。 我的意思是数学,它使读者想起遵循的规则。 我所说的数学也就是方程式,可以避免跳过那么多步骤,而让我们思考一下一行和另一行之间发生了什么。 因为有时候我们确实需要有人牵着手走在陌生概念领域。 衷心希望我能与您取得联系。
参考文献- M.Amine,《神经网络的内部运作》,《我的Colab笔记本》(2020年)。
- 阿米(Amine),《神经网络的内部运作》,我的要旨,(2020年)
- Géron,使用Scikit-Learn,Keras和TensorFlow进行动手机器学习,(2019年)。
- Gugger,numpy中的简单神经网络,(2018年)。
- Howard,Fast.ai:从基础第9课开始的深度学习(2019).
- Pytorch文档。
(本文翻译自Mehdi Amine的文章《Dismantling Neural Networks to Understand the Inner Workings with Math and Pytorch》,参考:https://towardsdatascience.com/dismantling-neural-networks-to-understand-the-inner-workings-with-math-and-pytorch-beac8760b595)