原文:
www.kdnuggets.com/2020/03/build-artificial-neural-network-scratch-part-2.html
1. 谷歌网络安全证书 - 快速进入网络安全职业轨道。
2. 谷歌数据分析专业证书 - 提升你的数据分析技能
3. 谷歌 IT 支持专业证书 - 支持组织的 IT 需求
在我之前的文章中,从头构建人工神经网络(ANN):第一部分,我们开始讨论什么是人工神经网络;我们看到如何用 Python 从头创建一个简单的神经网络,具有一个输入层和一个输出层。这样的神经网络被称为感知器。然而,现实世界的神经网络能够执行复杂任务,如图像分类和股票市场分析,除了输入层和输出层,还包含多个隐藏层。
在上一篇文章中,我们总结了感知器能够找到线性决策边界。我们使用感知器预测一个人是否患糖尿病,使用的是一个虚拟数据集。然而,感知器无法找到非线性决策边界。
在本文中,我们将开发一个具有一个输入层、一个隐藏层和一个输出层的神经网络。我们将看到我们开发的神经网络将能够找到非线性边界。
让我们开始生成一个可以玩耍的数据集。幸运的是,scikit-learn 提供了一些有用的数据集生成器,因此我们不需要自己编写代码。我们将使用 make_moons 函数。
from sklearn import datasets
np.random.seed(0)
feature_set, labels = datasets.make_moons(300, noise=0.20)
plt.figure(figsize=(10,7))
plt.scatter(feature_set[:,0], feature_set[:,1], c=labels, cmap=plt.cm.Spectral)
我们生成的数据集有两个类别,以红色和蓝色点绘制。你可以将蓝色点视为男性患者,红色点视为女性患者,x 轴和 y 轴表示医疗测量。
我们的目标是训练一个机器学习分类器,该分类器根据 x
和 y
坐标预测正确的类别(男性或女性)。请注意,数据是非线性可分的,我们无法绘制一条直线将两个类别分开。这意味着线性分类器,例如没有隐藏层的 ANN 或者逻辑回归,将无法拟合数据,除非你手动工程化对给定数据集有效的非线性特征(例如多项式)。
这是我们的简单网络:
我们有两个输入:x1 和 x2。有一个隐藏层,包含 3 个单元(节点):h1, h2 和 h3。最后,有两个输出:y1 和 y2。连接它们的箭头是权重。共有两个权重矩阵:w 和 u。w 权重连接输入层和隐藏层。u 权重连接隐藏层和输出层。我们使用了字母 w 和 u,以便更容易跟踪计算。你还可以看到,我们将输出 y1 和 y2 与目标 t1 和 t2 进行比较。
在进行计算之前,我们需要引入最后一个字母。让 a 代表激活之前的线性组合。因此,我们有:
和
由于我们无法穷举所有激活函数和所有损失函数,我们将专注于两种最常见的:sigmoid 激活函数和 L2-norm 损失。有了这些新信息和新符号,输出 y 等于激活的线性组合。
因此,对于输出层,我们有:
而对于隐藏层:
我们将分别考察输出层和隐藏层的反向传播,因为方法有所不同。
我想提醒你:
sigmoid 函数是:
其导数是:
为了得到更新规则:
我们必须计算
让我们考虑单个权重 uij。损失函数关于 uij 的偏导数等于:
其中 i 对应于前一层(本次变换的输入层),j 对应于下一层(变换的输出层)。偏导数的计算是简单地按照链式法则进行的。
按照 L2-norm 损失的导数。
按照 sigmoid 导数进行。
最后,第三个偏导数只是以下的导数:
所以,
替换上述表达式中的偏导数,我们得到:
因此,输出层单个权重的更新规则为:
类似于输出层的反向传播,单个权重的更新规则,wij 将依赖于:
按照链式法则。利用目前我们为使用 sigmoid 激活函数和线性模型变换得到的结果,我们得到:
和
反向传播的实际问题来源于术语
这是因为没有“隐藏”的目标。你可以参考下面对权重 w11 的解决方案。在进行计算时,建议查看上面显示的神经网络图。
从这里,我们可以计算
这正是我们想要的。最终的表达式是:
这个方程的广义形式是:
使用输出层和隐藏层的反向传播结果,我们可以将它们合并为一个公式,总结反向传播,考虑 L2 范数损失和 sigmoid 激活。
对于隐藏层
现在让我们从头开始在 Python 中实现刚才讨论的神经网络。我们将再次尝试分类我们上面创建的非线性数据。
我们从定义一些用于梯度下降的有用变量和参数开始,比如训练数据集的大小、输入层和输出层的维度。
num_examples = len(X) # training set size
nn_input_dim = 2 # input layer dimensionality
nn_output_dim = 2 # output layer dimensionality
同时定义梯度下降的参数。
epsilon = 0.01 # learning rate for gradient descent
reg_lambda = 0.01 # regularization strength
首先,让我们实现上述定义的损失函数。我们用它来评估模型的表现:
# Helper function to evaluate the total loss on the dataset
def calculate_loss(model, X, y):
num_examples = len(X) # training set size
W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
# Forward propagation to calculate our predictions
z1 = X.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
# Calculating the loss
corect_logprobs = -np.log(probs[range(num_examples), y])
data_loss = np.sum(corect_logprobs)
# Add regulatization term to loss (optional)
data_loss += Config.reg_lambda / 2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
return 1\. / num_examples * data_loss
我们还实现了一个辅助函数来计算网络的输出。它执行前向传播并返回概率最高的类别。
def predict(model, x):
W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
# Forward propagation
z1 = x.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
return np.argmax(probs, axis=1)
最后,这里是训练我们神经网络的函数。它实现了使用上述反向传播导数的批量梯度下降。
这个函数学习神经网络的参数并返回模型。
nn_hdim: 隐藏层中节点的数量
num_passes: 梯度下降训练数据的遍历次数
print_loss: 如果为 True,每 1000 次迭代打印一次损失
def build_model(X, y, nn_hdim, num_passes=20000, print_loss=False):
# Initialize the parameters to random values. We need to learn these.
num_examples = len(X)
np.random.seed(0)
W1 = np.random.randn(Config.nn_input_dim, nn_hdim) / np.sqrt(Config.nn_input_dim)
b1 = np.zeros((1, nn_hdim))
W2 = np.random.randn(nn_hdim, Config.nn_output_dim) / np.sqrt(nn_hdim)
b2 = np.zeros((1, Config.nn_output_dim))# This is what we return at the end
model = {}# Gradient descent. For each batch...
for i in range(0, num_passes):# Forward propagation
z1 = X.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)# Backpropagation
delta3 = probs
delta3[range(num_examples), y] -= 1
dW2 = (a1.T).dot(delta3)
db2 = np.sum(delta3, axis=0, keepdims=True)
delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
dW1 = np.dot(X.T, delta2)
db1 = np.sum(delta2, axis=0)# Add regularization terms (b1 and b2 don't have regularization terms)
dW2 += Config.reg_lambda * W2
dW1 += Config.reg_lambda * W1# Gradient descent parameter update
W1 += -Config.epsilon * dW1
b1 += -Config.epsilon * db1
W2 += -Config.epsilon * dW2
b2 += -Config.epsilon * db2# Assign new parameters to the model
model = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}# Optionally print the loss.
# This is expensive because it uses the whole dataset, so we don't want to do it too often.
if print_loss and i % 1000 == 0:
print("Loss after iteration %i: %f" % (i, calculate_loss(model, X, y)))return model
最后是主要方法:
def main():
X, y = generate_data()
model = build_model(X, y, 3, print_loss=True)
visualize(X, y, model)
每 1000 次迭代后打印损失:
当隐藏层节点数量为 3 时的分类
现在让我们了解一下隐藏层大小的变化如何影响结果。
hidden_layer_dimensions = [1, 2, 3, 4, 5, 20, 50]
for i, nn_hdim in enumerate(hidden_layer_dimensions):
plt.subplot(5, 2, i+1)
plt.title('Hidden Layer size %d' % nn_hdim)
model = build_model(X, y,nn_hdim, 20000, print_loss=False)
plot_decision_boundary(lambda x:predict(model,x), X, y)
plt.show()
我们可以看到,低维度的隐藏层很好地捕捉了数据的一般趋势。较高的维度容易导致过拟合。它们是在“记忆”数据,而不是拟合一般的形状。
如果我们在单独的测试集上评估我们的模型,较小隐藏层大小的模型由于更好的泛化能力可能会表现得更好。我们可以通过更强的正则化来对抗过拟合,但选择合适的隐藏层大小是更“经济”的解决方案。
你可以在这个 GitHub 仓库中获取完整的代码。
nageshsinghc4/人工神经网络从零开始-python
所以在这篇文章中,我们看到如何从数学上推导出一个具有一个隐藏层的神经网络,并且我们还用 NumPy 从零开始创建了一个具有 1 个隐藏层的神经网络。
好了,这就结束了关于从零开始构建人工神经网络的两篇文章系列**。** 希望你们喜欢阅读,欢迎在评论区分享你的评论/想法/反馈。
简介:Nagesh Singh Chauhan 是 CirrusLabs 的大数据开发人员。他在电信、分析、销售、数据科学等各个领域拥有超过 4 年的工作经验,并在各种大数据组件方面具有专业知识。
原文。 经授权转载。
相关:
-
从零开始构建人工神经网络:第一部分
-
只有 NumPy:从头理解和创建神经网络的计算图
-
如何将图片转换为数字