随着ChatGPT的爆火以及最近各种爆发的大模型竞赛,人工智能行业逐渐走入了群众的眼球。作为喜欢折腾各种技术的爱好者,自然也期望能了解一些其间的原理。但想要更好的了解AI领域的常识,我想从深度学习开端是不为过的,由于早前现已学习过吴恩达教授的Machine Learning课程,因而本次也是经过他的另一门专项课程,Deep Learning Specialization来学习深度学习。本文首要以第一门课为参考,测验通俗的带咱们入门深度学习。

1. 神经网络简介及根底概念

神经网络是一种模拟人脑神经元网络进行分布式并行信息处理的算法模型。它由大量的节点(或称为神经元)相互衔接构成,每个神经元可以运用简略的信号处理函数处理输入信号,并将处理成果传递给后续神经元。这些核算的方针是最小化网络的猜测过错,这是经过不断调整网络中的参数(即神经元之间的衔接权重)来完成的。如图是几个常见神经网络的例子,包含规范神经网络,卷积神经网络CNN以及循环神经网络RNN。

深度学习与神经网络入门
为了更好的深化研究这个主题,咱们先介绍一下神经网络中的几个要害概念。

1.1 丢失函数(Loss Function)

丢失函数衡量了咱们的模型对单个样本的猜测成果与实在成果之间的差异。举个例子,关于二分类问题,常用的丢失函数是穿插熵丢失函数(Cross Entropy Loss)。假定咱们的模型对一个样本属于正类的猜测概率为y\hat{y},而该样本的实在标签为yyyy为0或1),那么穿插熵丢失函数可以定义为:

L(y,y)=−[ylog(y)+(1−y)log(1−y)]L(\hat{y}, y) = -[ylog(\hat{y}) + (1-y)log(1-\hat{y})]

这个函数刻画了实在标签和猜测概率之间的差异:当实在标签与猜测概率挨近时,丢失挨近于零;当实在标签与猜测概率相差较大时,丢失将变得十分大。

1.2 价值函数(Cost Function)

尽管丢失函数衡量了单个样本的猜测差错,但咱们一般会对整个练习集的猜测差错进行衡量,这就需求用到价值函数。价值函数是一切样本丢失函数值的平均。关于穿插熵丢失函数,假定咱们有mm个样本,那么价值函数JJ可以定义为:

J(w,b)=−1m∑i=1m[y(i)log(y(i))+(1−y(i))log(1−y(i))]J(w, b) = -\frac{1}{m}\sum_{i=1}^{m}[y^{(i)}log(\hat{y}^{(i)}) + (1-y^{(i)})log(1-\hat{y}^{(i)})]

在神经网络中,咱们的方针便是找到最佳的wwbb,使得价值函数J(w,b)J(w, b)最小,这一般经过梯度下降等优化算法来完成。

1.3 梯度下降(Gradient Descent)

为了找到最小化成本函数的参数,咱们一般运用一种被称为梯度下降的优化算法。在每一次迭代中,咱们先核算成本函数关于每个参数的梯度,然后更新参数:

w=w−∂J∂ww = w – \alpha \frac{\partial J}{\partial w}

b=b−∂J∂bb = b – \alpha \frac{\partial J}{\partial b}

其间,\alpha学习率(learning rate),操控更新步长的巨细。∂J∂w\frac{\partial J}{\partial w}∂J∂b\frac{\partial J}{\partial b}是价值函数JJ关于参数wwbb的偏导数,表明JJ在当前参数方位的改动率。

经过多次迭代,咱们可以逐渐迫临最小化价值函数的参数,这样咱们的模型也就得到了优化。

1.3.1 批量梯度下降,随机梯度下降和小批量梯度下降

依据数据集的运用方法,咱们可以将梯度下降分为批量梯度下降(Batch Gradient Descent)、随机梯度下降(Stochastic Gradient Descent)和小批量梯度下降(Mini-batch Gradient Descent)。

  • 批量梯度下降:在每一次迭代中,咱们运用整个数据集来核算梯度和更新参数。这种方法的优点是方向准确,不容易陷入部分最优,但缺陷是当数据集很大时,每次迭代的核算量就会很大,核算速度慢。
  • 随机梯度下降:在每一次迭代中,咱们只运用一个样原本核算梯度和更新参数。这种方法的优点是核算速度快,可以快速收敛,但缺陷是由于每次只运用一个样本,更新的方向会有很大的波动,或许会错失大局最优。
  • 小批量梯度下降:这是上述两种方法的折中,每次迭代运用一小批样原本核算梯度和更新参数。这种方法结合了批量梯度下降和随机梯度下降的优点,既能确保必定的核算速度,又能确保梯度方向的准确性,是实践运用中最常用的方法。

1.3.2 梯度下降的应战及应对战略

尽管梯度下降是一种强大的优化东西,但在实践中,咱们或许会遇到一些应战:

  • 部分最优:梯度下降或许会陷入部分最优,而无法找到大局最优。处理此问题的一种常见战略是运用随机初始化,或许运用更杂乱的优化战略,如带动量的梯度下降,Adam等。
  • 梯度消失和梯度爆破:在深度神经网络中,梯度或许会在反向传达进程中变得十分小(梯度消失)或十分大(梯度爆破)。处理这个问题的战略包含运用ReLU等非饱和激活函数,或许运用批量归一化,残差结构等。
  • 挑选合适的学习率:假如学习率过大,或许会导致梯度下降无法收敛;假如学习率过小,梯度下降的速度或许会十分慢。处理此问题的战略包含学习率衰减,或许运用自适应学习率的优化器,如Adam。

经过深化了解梯度下降,咱们可以更好地了解神经网络的练习进程,并可以对练习进程进行更好的操控和优化。

1.4 神经网络中的数学符号

神经网络中触及到许多数学符号,下面咱们来了解一些最基本的符号。

  • x(i)x^{(i)}:表明第ii个样本的特征向量。
  • y(i)y^{(i)}:表明第ii个样本的实在标签。
  • y(i)\hat{y}^{(i)}:表明第ii个样本的猜测标签。
  • w[l]w^{[l]}:表明第ll层的权重矩阵。
  • b[l]b^{[l]}:表明第ll层的偏置向量。
  • a[l]a^{[l]}:表明第ll层的激活值。
  • z[l]z^{[l]}:表明第ll层的线性输出,即z[l]=w[l]a[l−1]+b[l]z^{[l]} = w^{[l]}a^{[l-1]}+b^{[l]}
  • mm:表明样本数量。
  • n[l]n^{[l]}:表明第ll层的节点数量。
  • nxn_x:表明输入层的节点数量,也即特征数量。
  • nyn_y:表明输出层的节点数量,也即标签类别数量。 其实首要要记住就这3点:
  • 右上角中括号,表明第ll
  • 右上角圆括号,表明第ii个example
  • 右下角数字,表明第nn个神经元

这些符号构成了神经网络的基本言语,了解了它们,你就可以了解神经网络的作业原理,以及怎么经过数学方法将其完成。这些公式现在记不住也没关系,后文中看到后可以再过来查询。

2. 激活函数

在神经网络中,激活函数扮演着重要的人物。它为神经网络添加了非线性因素,使得神经网络可以拟合更杂乱的模型。在本章中,咱们将详细介绍几种常见的激活函数及其特性。

2.1 Sigmoid函数

Sigmoid函数是最早被运用在神经网络中的激活函数,表达式为: a=(z)=11+e−za = \sigma(z) = \frac{1}{1+e^{-z}} 它的特点是其输出在(0, 1)之间,因而可以被解释为概率。并且,当z趋向于正无量,a趋近于1,而当z趋向于负无量,a趋近于0。,因而在二分类问题中常常被用作输出层的激活函数,将输出解释为概率。

深度学习与神经网络入门

Sigmoid函数在分类算法Logistic回归中被广泛运用,这个算法首要用于二分类问题。它的方针是找到一个模型,可以依据输入的特征猜测出一个事件产生的概率。这个猜测的概率便是Logistic回归模型的输出。

可是,Sigmoid函数在输入值的绝对值较大时,函数的梯度挨近于0,这将导致梯度消失,使得神经网络的练习变得困难。此外,Sigmoid函数的输出不是以0为中心的,这或许会导致练习进程中的收敛速度变慢。

2.2 Tanh函数

Tanh函数可以看作是Sigmoid函数的扩展,它将输入压缩到(-1,1)。Tanh函数的表达式为: a=tanh⁡(z)=ez−e−zez+e−za = \tanh(z) = \frac{e^z-e^{-z}}{e^z+e^{-z}} 由于Tanh函数的输出是以0为中心的,因而在实践中,Tanh函数一般比Sigmoid函数的表现要好。

深度学习与神经网络入门

可是,Tanh函数依然存在梯度消失的问题,当输入值的绝对值较大时,函数的梯度挨近于0。

2.3 ReLU函数(Rectified Linear Unit 、线性整流函数、修正线性单元)

ReLU函数是现在在神经网络中常用的激活函数。ReLU函数的表达式为: a=max⁡(0,z)a = \max(0,z) ReLU函数在x>0时坚持输入不变,在x<0时输出为0。由于ReLU函数在x>0时梯度为1,在x<0时梯度为0,因而ReLU函数在必定程度上缓解了梯度消失的问题。

深度学习与神经网络入门

可是,ReLU函数在x<0时完全不激活,这或许会导致一些神经元逝世,即在练习进程中永久不会被激活。此外,ReLU函数的输出也不是以0为中心的。

2.4 Leaky ReLU函数

为了处理ReLU函数在x<0时或许导致的神经元逝世问题,人们提出了Leaky ReLU函数。Leaky ReLU函数的表达式为: a=max⁡(0.01z,z)a = \max(0.01z, z) 与ReLU函数相比,Leaky ReLU函数在x<0时的斜率为0.01,这意味着Leaky ReLU函数在x<0时依然有小的梯度,因而可以缓解神经元逝世的问题。

深度学习与神经网络入门

激活函数是神经网络中的重要组成部分,了解不同激活函数的性质和适用场景,可以协助咱们更好地设计和优化神经网络。

3. 前向传达

在深度学习中,前向传达(Forward Propagation)是一种十分根底且要害的进程。前向传达触及将输入数据传递经过神经网络并核算出成果。本章将详细阐述前向传达的进程,并给出一些相关的数学细节。

3.1 前向传达的概念

首先,咱们来了解什么是前向传达。在神经网络中,每一层的节点都是由前一层的节点经过某种核算得来的,这个核算的进程便是前向传达。具体地说,关于每一层,咱们都会首先核算一个线性组合(即,对输入进行加权求和),然后再经过一个激活函数进行非线性改换。

深度学习与神经网络入门

在数学上,关于第ll层的每个节点jj,其线性组合可以写作zj[l]=∑iwij[l]ai[l−1]+bj[l]z_j^{[l]} = \sum_i w_{ij}^{[l]} a_i^{[l-1]} + b_j^{[l]},其间wij[l]w_{ij}^{[l]}是衔接第l−1l-1层的第ii个节点和第ll层的第jj个节点的权重,ai[l−1]a_i^{[l-1]}是第l−1l-1层的第ii个节点的激活值,bj[l]b_j^{[l]}是第ll层的第jj个节点的偏置。接下来,咱们会将zj[l]z_j^{[l]}输入到一个激活函数g[l]g^{[l]}中,得到该节点的激活值aj[l]=g[l](zj[l])a_j^{[l]} = g^{[l]}(z_j^{[l]})

3.2 线性部分的核算

为了更有效地处理多个节点和多个样本,咱们一般运用矩阵和向量的形式来表明和核算线性部分。假定咱们现在有mm个样本,那么咱们可以将这些样本的输入表明为一个nxmn_x \times m的矩阵XX,其间nxn_x是输入的巨细(即,第0层,也便是输入层的节点数),mm是样本的数量。

关于第ll层,咱们可以将其权重表明为一个n[l]n[l−1]n^{[l]} \times n^{[l-1]}的矩阵W[l]W^{[l]},将其偏置表明为一个n[l]1n^{[l]} \times 1的向量b[l]b^{[l]},其间n[l]n^{[l]}是第ll层的节点数,n[l−1]n^{[l-1]}是第l−1l-1层的节点数。这样,关于一切的样本,咱们可以一次性核算第ll层一切节点的线性组合,即Z[l]=W[l]A[l−1]+b[l]Z^{[l]} = W^{[l]} A^{[l-1]} + b^{[l]},其间A[l−1]A^{[l-1]}是第l−1l-1层一切节点的激活值。

3.3 非线性部分的核算

得到线性部分Z[l]Z^{[l]}后,咱们需求经过一个激活函数进行非线性改换。这儿的激活函数可以是任何非线性函数,常见的有Sigmoid函数、tanh函数、ReLU函数、Leaky ReLU函数等。运用激活函数的目的是为了引入非线性,使得神经网络可以迫临任何函数。假如咱们不运用激活函数,那么不管神经网络有多少层,其总是等价于一个线性模型,这大大限制了神经网络的表达能力。

关于第ll层,咱们将Z[l]Z^{[l]}输入到激活函数g[l]g^{[l]}中,得到激活值A[l]=g[l](Z[l])A^{[l]} = g^{[l]}(Z^{[l]})。相同,为了一次性处理一切的样本,咱们可以将A[l]A^{[l]}看作是一个n[l]mn^{[l]} \times m的矩阵,其间每一列对应一个样本,每一行对应一个节点。

3.4 成果的核算

当咱们核算到最终一层,也便是输出层时,咱们就得到了神经网络的输出成果。关于二分类问题,咱们一般运用Sigmoid函数作为输出层的激活函数,这样得到的成果可以看作是正类的概率;关于多分类问题,咱们一般运用softmax函数作为输出层的激活函数,这样得到的成果可以看作是每个类别的概率。

在前向传达结束后,咱们会核算丢失函数,比较猜测成果和实在标签的距离。然后在后向传达中,咱们会依据这个距离来更新网络的参数,使得猜测成果更挨近实在标签。这便是神经网络的练习进程。

Z[1]=W[1]x+b[1]Z^{[1]} = W^{[1]}x + b^{[1]}
A[1]=g[1](Z[1])A^{[1]} = g^{[1]}(Z^{[1]})
Z[2]=W[2]A[1]+b[2]Z^{[2]} = W^{[2]}A^{[1]} + b^{[2]}
A[2]=g[2](Z[2])A^{[2]} = g^{[2]}(Z^{[2]})

以上便是前向传达的进程,尽管触及到了一些线性代数微积分的常识,但总的来说,前向传达只是一种将输入数据转换为输出成果的进程,而这个进程是完全确定的,只依赖于输入数据和网络参数。了解了前向传达,咱们就了解了神经网络怎么从输入得到输出,也就了解了神经网络的“前半部分”。接下来咱们将评论后向传达,也便是神经网络的“后半部分”。

4. 后向传达

在前向传达进程中,咱们了解了怎么经过神经网络将输入数据转换为输出。可是,假如咱们期望优化神经网络的功能,让其能更好地猜测或分类,咱们需求一个可以衡量神经网络输出与期望输出之间差异的规范,这便是丢失函数。当丢失函数确定后,咱们的方针便是找到一种方法来减小这个丢失。这个方法便是后向传达,也便是经过求解丢失函数的梯度,然后依据这个梯度来更新神经网络的参数。本章咱们将深化评论后向传达的原理和进程。

4.1 梯度的核算

上文介绍过价值函数,咱们的方针便是找到一种方法来最小化这个价值函数。由于价值函数是神经网络参数的函数,所以咱们可以经过调整这些参数来改动价值函数的值。这便是梯度下降的基本思想:咱们核算价值函数关于每个参数的偏导数,也便是梯度,然后依照梯度的反方向更新参数

具体地说,关于第ll层的权重W[l]W^{[l]}和偏置b[l]b^{[l]},咱们需求核算价值函数关于它们的偏导数∂J∂W[l]\frac{\partial J}{\partial W^{[l]}}∂J∂b[l]\frac{\partial J}{\partial b^{[l]}}。由于神经网络输出是经过一系列杂乱的核算得到的,所以这个偏导数的核算需求运用链式法则。这个进程被称为反向传达,由于咱们是从最终一层开端,然后向前一层一层地传递梯度。

为了便利核算,咱们可以先核算一个中间量dZ[l]=∂J∂Z[l]dZ^{[l]} = \frac{\partial J}{\partial Z^{[l]}},这样咱们就可以写出 ∂J∂W[l]=1mdZ[l]A[l−1]T\frac{\partial J}{\partial W^{[l]}} = \frac{1}{m} dZ^{[l]} A^{[l-1]T} ∂J∂b[l]=1mnp.sum(dZ[l],axis=1,keepdims=True)\frac{\partial J}{\partial b^{[l]}} = \frac{1}{m} np.sum(dZ^{[l]}, axis=1, keepdims=True)其间np.sumnp.sum表明对矩阵进行求和,axis=1表明沿着行的方向求和,keepdims=True表明坚持原有的维度。

关于dZ[l]dZ^{[l]},咱们可以经过链式法则核算得到: dZ[l]=dA[l]∗g′[l](Z[l])dZ^{[l]} = dA^{[l]} * g’^{[l]}(Z^{[l]})其间∗*表明元素等级的乘法g′[l]g’^{[l]}表明激活函数的导数。 dA[l]=∂J∂A[l]=dZ[l+1]W[l+1]TdA^{[l]} = \frac{\partial J}{\partial A^{[l]}} = dZ^{[l+1]} W^{[l+1]T}

经过这种方法,咱们可以一层一层地向前传递梯度,直到第一层。这个进程便是后向传达。

4.2 参数的更新

得到了梯度后,咱们就可以更新参数了。具体地说,咱们依照以下的公式来更新第ll层的权重和偏置:

W[l]=W[l]−∂J∂W[l]W^{[l]} = W^{[l]} – \alpha \frac{\partial J}{\partial W^{[l]}}
b[l]=b[l]−∂J∂b[l]b^{[l]} = b^{[l]} – \alpha \frac{\partial J}{\partial b^{[l]}}

其间,\alpha学习率,是一个超参数(Hyperparameters),用于操控参数更新的步长。这儿的 ∂J∂W[l]\frac{\partial J}{\partial W^{[l]}}∂J∂b[l]\frac{\partial J}{\partial b^{[l]}} 便是咱们在反向传达中核算出的梯度。经过这种方法,咱们可以一点点地降低丢失函数的值,提升神经网络的功能。

4.3 后向传达公式总结

dZ[L]=A[L]−YdZ^{[L]} = A^{[L]} – Y
dW[L]=1mdZ[L]A[L−1]TdW^{[L]} = \frac{1}{m}dZ^{[L]}A^{{[L-1]}^T}
db[L]=1mnp.sum(dz[L],axis=1,keepdim=True)db^{[L]} = \frac{1}{m}np.sum(dz^{[L]}, axis=1, keepdim=True)
dZ[L−1]=dW[L]TdZ[L]∗g′[L](Z[L−1])dZ^{[L-1]} = dW^{{[L]}^T}dZ^{[L]} * g’^{[L]}(Z^{[L-1]})
……
dZ[1]=dW[L]TdZ[2]∗g′[1](Z[1])dZ^{[1]} = dW^{{[L]}^{T}}dZ^{[2]} * g’^{[1]}(Z^{[1]})
dW[1]=1mdZ[1]A[1]TdW^{[1]} = \frac{1}{m}dZ^{[1]}A^{{[1]}^T}
db[1]=1mnp.sum(dZ[1],axis=1,keepdims=True)db^{[1]} = \frac{1}{m}np.sum(dZ^{[1]}, axis=1, keepdims=True)

以上便是后向传达的进程。可以看到,后向传达首要是关于求解梯度和更新参数的进程。尽管触及到一些微积分和线性代数的常识,但总的来说,只需了解了梯度下降和链式法则,就能了解后向传达。了解了后向传达,咱们就了解了神经网络怎么依据输入和输出之间的差异来调整自身的参数,也就了解了神经网络的“后半部分”。在下一章中,咱们将运用python来完成一个简略的神经网络,经过这个实例,咱们将更深化地了解前向传达和后向传达。

5. Python完成神经网络

在了解了神经网络的基本原理后,咱们将在本章中用Python完成一个简略的神经网络Demo。

5.1 神经网络的构建

这个进程包含初始化参数前向传达后向传达、参数更新、优化函数,模型定义。首先,咱们需求初始化参数。留意这儿的W要运用random初始化,而不是zeros,否则会导致第一层神经元核算出的成果都是0。

def initialize_parameters(n_x, n_h, n_y):
    np.random.seed(1)
    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros((n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros((n_y, 1))
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    return parameters

然后,咱们需求定义前向传达和后向传达的函数。

# 定义sigmoid函数
def sigmoid(Z):
    A = 1 / (1 + np.exp(-Z))
    cache = Z
    return A, cache
# 前向传达函数
def forward_propagation(X, parameters):
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]
    Z1 = np.dot(W1, X) + b1
    A1 = np.tanh(Z1)
    Z2 = np.dot(W2, A1) + b2
    A2, cache = sigmoid(Z2)
    cache = {"Z1": Z1,
             "A1": A1,
             "Z2": Z2,
             "A2": A2}
    return A2, cache
# 核算CostFunction
def compute_cost(A2, Y):
    m = Y.shape[1]
    logprobs = np.multiply(np.log(A2), Y) + np.multiply((1 - Y), np.log(1 - A2))
    cost = - np.sum(logprobs) / m
    cost = np.squeeze(cost)
    return cost
# 后向传达函数
def backward_propagation(parameters, cache, X, Y):
    m = X.shape[1]
    W1 = parameters["W1"]
    W2 = parameters["W2"]
    A1 = cache["A1"]
    A2 = cache["A2"]
    dZ2 = A2 - Y
    dW2 = (1 / m) * np.dot(dZ2, A1.T)
    db2 = (1 / m) * np.sum(dZ2, axis=1, keepdims=True)
    dZ1 = np.multiply(np.dot(W2.T, dZ2), 1 - np.power(A1, 2))
    dW1 = (1 / m) * np.dot(dZ1, X.T)
    db1 = (1 / m) * np.sum(dZ1, axis=1, keepdims=True)
    grads = {"dW1": dW1,
             "db1": db1,
             "dW2": dW2,
             "db2": db2}
    return grads
# 更新参数的函数
def update_parameters(parameters, grads, learning_rate=1.2):
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]
    dW1 = grads["dW1"]
    db1 = grads["db1"]
    dW2 = grads["dW2"]
    db2 = grads["db2"]
    W1 = W1 - learning_rate * dW1
    b1 = b1 - learning_rate * db1
    W2 = W2 - learning_rate * dW2
    b2 = b2 - learning_rate * db2
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    return parameters

接下来,咱们需求定义一个优化函数,用于进行多轮的练习,每轮练习中都会履行一次前向传达、核算丢失、履行一次后向传达和更新参数。

def optimize(parameters, X, Y, num_iterations=10000, learning_rate=1.2, print_cost=False):
    costs = []
    for i in range(0, num_iterations):
        A2, cache = forward_propagation(X, parameters)
        cost = compute_cost(A2, Y)
        grads = backward_propagation(parameters, cache, X, Y)
        parameters = update_parameters(parameters, grads, learning_rate)
        if print_cost and i % 1000 == 0:
            print ("Cost after iteration %i: %f" %(i, cost))
            costs.append(cost)
    return parameters, costs

最终,咱们需求定义一个模型函数,用于整合以上的一切过程,包含初始化参数、进行多轮的练习,得到练习好的参数。

def model(X_train, Y_train, X_test, Y_test, n_h=4, num_iterations=10000, learning_rate=1.2, print_cost=True):
    np.random.seed(3)
    n_x = X_train.shape[0]
    n_y = Y_train.shape[0]
    parameters = initialize_parameters(n_x, n_h, n_y)
    parameters, costs = optimize(parameters, X_train, Y_train, num_iterations, learning_rate, print_cost)
    return parameters, costs

经过以上的过程,咱们就得到了一个简略的三层神经网络模型。尽管这个模型很简略,但它涵盖了神经网络的核心思想,包含前向传达、核算丢失、后向传达和更新参数。期望经过这个实例,可以协助咱们更好地了解神经网络的原理和运行机制。

当然,实在的深度学习使命要比这个实例杂乱得多,比方或许会触及到更杂乱的网络结构、更杂乱的丢失函数、正则化、优化器、批归一化等等。可是,只需你了解了这个实例,就现已迈出了深度学习的第一步。