ChatGPT的爆火以及最近各种爆发的大模型竞争,人工智能行业逐渐走入了大众的眼球。作为喜欢折腾各种技术的爱好者,自然也希望能了解一些其中的原理。但想要更好的了解AI领域的知识,我想从深度学习开始是不为过的,因为早前已经学习过吴恩达教授的Machine Learning课程,因此本次也是通过他的另一门专项课程,Deep Learning Specialization来学习深度学习。本文主要以第一门课为参考,尝试通俗的带大家入门深度学习。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 神经网络是一种模拟人脑神经元网络进行分布式并行信息处理的算法模型。它由大量的节点(或称为神经元)相互连接构成,每个神经元可以使用简单的信号处理函数处理输入信号,并将处理结果传递给后续神经元。这些计算的目标是最小化网络的预测错误,这是通过不断调整网络中的参数(即神经元之间的连接权重)来实现的。如图是几个常见神经网络的例子,包括标准神经网络,卷积神经网络CNN以及循环神经网络RNN。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 为了更好的深入研究这个主题,我们先介绍一下神经网络中的几个关键概念。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 损失函数度量了我们的模型对单个样本的预测结果与真实结果之间的差异。举个例子,对于二分类问题,常用的损失函数是交叉熵损失函数(Cross Entropy Loss)。假设我们的模型对一个样本属于正类的预测概率为,而该样本的真实标签为(为0或1),那么交叉熵损失函数可以定义为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 这个函数刻画了真实标签和预测概率之间的差异:当真实标签与预测概率接近时,损失接近于零;当真实标签与预测概率相差较大时,损失将变得非常大。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 虽然损失函数衡量了单个样本的预测误差,但我们通常会对整个训练集的预测误差进行度量,这就需要用到代价函数。代价函数是所有样本损失函数值的平均。对于交叉熵损失函数,假设我们有个样本,那么代价函数可以定义为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 在神经网络中,我们的目标就是找到最佳的和,使得代价函数最小,这通常通过梯度下降等优化算法来实现。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 为了找到最小化成本函数的参数,我们通常使用一种被称为梯度下降的优化算法。在每一次迭代中,我们先计算成本函数关于每个参数的梯度,然后更新参数:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 其中,是学习率(learning rate),控制更新步长的大小。和是代价函数关于参数和的偏导数,表示在当前参数位置的变化率。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 通过多次迭代,我们可以逐步逼近最小化代价函数的参数,这样我们的模型也就得到了优化。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 根据数据集的使用方式,我们可以将梯度下降分为批量梯度下降(Batch Gradient Descent)、随机梯度下降(Stochastic Gradient Descent)和小批量梯度下降(Mini-batch Gradient Descent)。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 虽然梯度下降是一种强大的优化工具,但在实践中,我们可能会遇到一些挑战:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 通过深入理解梯度下降,我们可以更好地理解神经网络的训练过程,并能够对训练过程进行更好的控制和优化。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 神经网络中涉及到许多数学符号,下面我们来了解一些最基本的符号。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 这些符号构成了神经网络的基本语言,理解了它们,你就能够理解神经网络的工作原理,以及如何通过数学方式将其实现。这些公式现在记不住也没关系,后文中看到后可以再过来查询。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 在神经网络中,激活函数扮演着重要的角色。它为神经网络添加了非线性因素,使得神经网络可以拟合更复杂的模型。在本章中,我们将详细介绍几种常见的激活函数及其特性。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html Sigmoid函数是最早被使用在神经网络中的激活函数,表达式为: Sigmoid函数在分类算法Logistic回归中被广泛使用,这个算法主要用于二分类问题。它的目标是找到一个模型,可以根据输入的特征预测出一个事件发生的概率。这个预测的概率就是Logistic回归模型的输出。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 然而,Sigmoid函数在输入值的绝对值较大时,函数的梯度接近于0,这将导致梯度消失,使得神经网络的训练变得困难。此外,Sigmoid函数的输出不是以0为中心的,这可能会导致训练过程中的收敛速度变慢。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html Tanh函数可以看作是Sigmoid函数的扩展,它将输入压缩到(-1,1)。Tanh函数的表达式为: 然而,Tanh函数仍然存在梯度消失的问题,当输入值的绝对值较大时,函数的梯度接近于0。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html ReLU函数是目前在神经网络中常用的激活函数。ReLU函数的表达式为: 然而,ReLU函数在x<0时完全不激活,这可能会导致一些神经元死亡,即在训练过程中永远不会被激活。此外,ReLU函数的输出也不是以0为中心的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 为了解决ReLU函数在x<0时可能导致的神经元死亡问题,人们提出了Leaky ReLU函数。Leaky ReLU函数的表达式为: 激活函数是神经网络中的重要组成部分,理解不同激活函数的性质和适用场景,可以帮助我们更好地设计和优化神经网络。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 在深度学习中,前向传播(Forward Propagation)是一种非常基础且关键的过程。前向传播涉及将输入数据传递通过神经网络并计算出结果。本章将详细阐述前向传播的过程,并给出一些相关的数学细节。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 首先,我们来理解什么是前向传播。在神经网络中,每一层的节点都是由前一层的节点通过某种计算得来的,这个计算的过程就是前向传播。具体地说,对于每一层,我们都会首先计算一个线性组合(即,对输入进行加权求和),然后再通过一个激活函数进行非线性变换。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 在数学上,对于第层的每个节点,其线性组合可以写作,其中是连接第层的第个节点和第层的第个节点的权重,是第层的第个节点的激活值,是第层的第个节点的偏置。接下来,我们会将输入到一个激活函数中,得到该节点的激活值。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 为了更有效地处理多个节点和多个样本,我们通常使用矩阵和向量的形式来表示和计算线性部分。假设我们现在有个样本,那么我们可以将这些样本的输入表示为一个的矩阵,其中是输入的大小(即,第0层,也就是输入层的节点数),是样本的数量。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 对于第层,我们可以将其权重表示为一个的矩阵,将其偏置表示为一个的向量,其中是第层的节点数,是第层的节点数。这样,对于所有的样本,我们可以一次性计算第层所有节点的线性组合,即,其中是第层所有节点的激活值。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 得到线性部分后,我们需要通过一个激活函数进行非线性变换。这里的激活函数可以是任何非线性函数,常见的有Sigmoid函数、tanh函数、ReLU函数、Leaky ReLU函数等。使用激活函数的目的是为了引入非线性,使得神经网络可以逼近任何函数。如果我们不使用激活函数,那么无论神经网络有多少层,其总是等价于一个线性模型,这大大限制了神经网络的表达能力。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 对于第层,我们将输入到激活函数中,得到激活值。同样,为了一次性处理所有的样本,我们可以将看作是一个的矩阵,其中每一列对应一个样本,每一行对应一个节点。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 当我们计算到最后一层,也就是输出层时,我们就得到了神经网络的输出结果。对于二分类问题,我们通常使用Sigmoid函数作为输出层的激活函数,这样得到的结果可以看作是正类的概率;对于多分类问题,我们通常使用softmax函数作为输出层的激活函数,这样得到的结果可以看作是每个类别的概率。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 在前向传播结束后,我们会计算损失函数,比较预测结果和真实标签的差距。然后在后向传播中,我们会根据这个差距来更新网络的参数,使得预测结果更接近真实标签。这就是神经网络的训练过程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 以上就是前向传播的过程,虽然涉及到了一些线性代数和微积分的知识,但总的来说,前向传播只是一种将输入数据转换为输出结果的过程,而这个过程是完全确定的,只依赖于输入数据和网络参数。理解了前向传播,我们就理解了神经网络如何从输入得到输出,也就理解了神经网络的“前半部分”。接下来我们将讨论后向传播,也就是神经网络的“后半部分”。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 在前向传播过程中,我们了解了如何通过神经网络将输入数据转换为输出。然而,如果我们希望优化神经网络的性能,让其能更好地预测或分类,我们需要一个能够衡量神经网络输出与期望输出之间差异的标准,这就是损失函数。当损失函数确定后,我们的目标就是找到一种方法来减小这个损失。这个方法就是后向传播,也就是通过求解损失函数的梯度,然后根据这个梯度来更新神经网络的参数。本章我们将深入讨论后向传播的原理和过程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 上文介绍过代价函数,我们的目标就是找到一种方法来最小化这个代价函数。因为代价函数是神经网络参数的函数,所以我们可以通过调整这些参数来改变代价函数的值。这就是梯度下降的基本思想:我们计算代价函数关于每个参数的偏导数,也就是梯度,然后按照梯度的反方向更新参数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 具体地说,对于第层的权重和偏置,我们需要计算代价函数关于它们的偏导数和。由于神经网络输出是通过一系列复杂的计算得到的,所以这个偏导数的计算需要使用链式法则。这个过程被称为反向传播,因为我们是从最后一层开始,然后向前一层一层地传递梯度。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 为了方便计算,我们可以先计算一个中间量,这样我们就可以写出 对于,我们可以通过链式法则计算得到: 通过这种方式,我们可以一层一层地向前传递梯度,直到第一层。这个过程就是后向传播。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 得到了梯度后,我们就可以更新参数了。具体地说,我们按照以下的公式来更新第层的权重和偏置:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 其中,是学习率,是一个超参数(Hyperparameters),用于控制参数更新的步长。这里的 和 就是我们在反向传播中计算出的梯度。通过这种方式,我们可以一点点地降低损失函数的值,提升神经网络的性能。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 以上就是后向传播的过程。可以看到,后向传播主要是关于求解梯度和更新参数的过程。虽然涉及到一些微积分和线性代数的知识,但总的来说,只要理解了梯度下降和链式法则,就能理解后向传播。理解了后向传播,我们就理解了神经网络如何根据输入和输出之间的差异来调整自身的参数,也就理解了神经网络的“后半部分”。在下一章中,我们将使用python来实现一个简单的神经网络,通过这个实例,我们将更深入地理解前向传播和后向传播。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 在了解了神经网络的基本原理后,我们将在本章中用Python实现一个简单的神经网络Demo。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 这个过程包括初始化参数、前向传播和后向传播、参数更新、优化函数,模型定义。首先,我们需要初始化参数。注意这里的W要使用random初始化,而不是zeros,否则会导致第一层神经元计算出的结果都是0。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 通过以上的步骤,我们就得到了一个简单的三层神经网络模型。虽然这个模型很简单,但它涵盖了神经网络的核心思想,包括前向传播、计算损失、后向传播和更新参数。希望通过这个实例,能够帮助大家更好地理解神经网络的原理和运行机制。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html 当然,真实的深度学习任务要比这个实例复杂得多,比如可能会涉及到更复杂的网络结构、更复杂的损失函数、正则化、优化器、批归一化等等。但是,只要你理解了这个实例,就已经迈出了深度学习的第一步。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html1. 神经网络简介及基础概念
1.1 损失函数(Loss Function)
1.2 代价函数(Cost Function)
1.3 梯度下降(Gradient Descent)
1.3.1 批量梯度下降,随机梯度下降和小批量梯度下降
1.3.2 梯度下降的挑战及应对策略
1.4 神经网络中的数学符号
其实主要要记住就这3点:2. 激活函数
2.1 Sigmoid函数
它的特点是其输出在(0, 1)之间,因此可以被解释为概率。并且,当z趋向于正无穷,a趋近于1,而当z趋向于负无穷,a趋近于0。,因此在二分类问题中常常被用作输出层的激活函数,将输出解释为概率。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html2.2 Tanh函数
由于Tanh函数的输出是以0为中心的,因此在实践中,Tanh函数通常比Sigmoid函数的表现要好。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html2.3 ReLU函数(Rectified Linear Unit 、线性整流函数、修正线性单元)
ReLU函数在x>0时保持输入不变,在x<0时输出为0。由于ReLU函数在x>0时梯度为1,在x<0时梯度为0,因此ReLU函数在一定程度上缓解了梯度消失的问题。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html2.4 Leaky ReLU函数
与ReLU函数相比,Leaky ReLU函数在x<0时的斜率为0.01,这意味着Leaky ReLU函数在x<0时仍然有小的梯度,因此可以缓解神经元死亡的问题。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html3. 前向传播
3.1 前向传播的概念
3.2 线性部分的计算
3.3 非线性部分的计算
3.4 结果的计算
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html4. 后向传播
4.1 梯度的计算
其中表示对矩阵进行求和,axis=1
表示沿着行的方向求和,keepdims=True
表示保持原有的维度。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html
其中表示元素级别的乘法,表示激活函数的导数。
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html4.2 参数的更新
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html4.3 后向传播公式总结
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ai/53813.html5. Python实现神经网络
5.1 神经网络的构建
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