原文标题 :Deep Learning with Python: Neural Networks (complete tutorial)
使用 Python 进行深度学习:神经网络(完整教程)
使用 TensorFlow 构建、绘制和解释人工神经网络
Summary
在本文中,我将展示如何使用 Python 构建神经网络,以及如何使用可视化向企业解释深度学习,并为模型预测创建解释器。
深度学习是一种机器学习,它模仿人类获得某些类型知识的方式,与标准模型相比,它多年来变得更加流行。虽然传统算法是线性的,但深度学习模型,通常是神经网络,堆叠在一个越来越复杂和抽象的层次结构中(因此是深度学习中的“深度”)。[0]
神经网络是基于连接单元(神经元)的集合,这些单元就像大脑中的突触一样,可以将信号传递给其他神经元,因此,它们就像相互连接的脑细胞一样,可以在更多的时间里学习和做出决定。类人的方式。[0]
今天,深度学习如此流行,以至于许多公司都想使用它,即使他们并不完全理解它。通常,数据科学家首先必须为业务简化这些复杂的算法,然后解释和证明模型的结果,这对于神经网络来说并不总是那么简单。我认为最好的方法是通过可视化。
我将介绍一些有用的 Python 代码,这些代码可以很容易地应用于其他类似情况(只需复制、粘贴、运行),并通过注释遍历每一行代码,以便您可以复制示例。
特别是,我将通过:
- 环境设置,tensorflow 与 pytorch。
- 人工神经网络分解、输入、输出、隐藏层、激活函数。
- 使用深度神经网络进行深度学习。
- 使用 tensorflow/keras 进行模型设计。
- 使用 python 可视化神经网络。
- Model training & testing.
- Explainability with shap.
Setup
构建神经网络有两个主要库:TensorFlow(由 Google 开发)和 PyTorch(由 Facebook 开发)。他们可以执行类似的任务,但前者更适合生产,而后者更适合构建快速原型,因为它更容易学习。[0][1]
这两个库受到社区和企业的青睐,因为它们可以利用 NVIDIA GPU 的强大功能。这对于处理大型数据集(如文本语料库或图像库)非常有用,有时甚至是必要的。
在本教程中,我将使用 TensorFlow 和 Keras,这是一种比纯 TensorFlow 和 PyTorch 更用户友好的高级模块,虽然速度会慢一些。[0]
第一步是通过终端安装TensorFlow:
pip install tensorflow
如果要启用 GPU 支持,可以阅读官方文档或遵循本指南。设置完成后,您的 Python 指令将由您的机器翻译成 CUDA 并由 GPU 处理,因此您的模型将运行得更快。[0][1][2]
现在我们可以在笔记本上导入 TensorFlow Keras 的主要模块并开始编码:
from tensorflow.keras import models, layers, utils, backend as K
import matplotlib.pyplot as plt
import shap
Artificial Neural Networks
ANN 由具有输入和输出维度的层组成。后者由神经元(也称为“节点”)的数量决定,这是一个通过激活函数(帮助神经元打开/关闭)连接加权输入的计算单元。与大多数机器学习算法一样,权重在训练期间随机初始化和优化,以最小化损失函数。
这些层可以分组为:
- 输入层负责将输入向量传递给神经网络。如果我们有一个包含 3 个特征的矩阵(形状 N x 3),则该层将 3 个数字作为输入,并将相同的 3 个数字传递给下一层。
- 隐藏层代表中间节点,它们对数字进行多次变换以提高最终结果的准确性,输出由神经元的数量定义。
- 返回神经网络最终输出的输出层。如果我们进行简单的二元分类或回归,输出层应该只有 1 个神经元(因此它只返回 1 个数字)。在具有 5 个不同类别的多类别分类的情况下,输出层应有 5 个神经元。
ANN 最简单的形式是感知器,一个只有一层的模型,与线性回归模型非常相似。询问感知器内部发生了什么相当于询问多层神经网络的单个节点内部发生了什么……让我们分解一下。[0]
假设我们有一个包含 N 行、3 个特征和 1 个目标变量的数据集(即二进制 1/0):
就像在所有其他机器学习用例中一样,我们将训练一个模型来逐行使用特征来预测目标。让我们从第一行开始:
“训练模型”是什么意思?搜索数学公式中的最佳参数,以最大限度地减少预测误差。在回归模型(即线性回归)中,您必须找到最佳权重,在基于树的模型(即随机森林)中,它是关于找到最佳分裂点……
通常,权重是随机初始化的,然后随着学习的进行进行调整。在这里,我将它们全部设置为 1:
到目前为止,我们还没有做任何与线性回归不同的事情(这对于企业来说非常简单易懂)。现在,这是从线性模型 Σ(xi*wi)=Y 升级到非线性模型 f(Σ(xi*wi))=Y … 进入激活函数。
激活函数定义了该节点的输出。有很多甚至可以创建一些自定义函数,您可以在官方文档中找到详细信息并查看此备忘单。如果我们在示例中设置一个简单的线性函数,那么我们将与线性回归模型没有区别。[0][1]
我将使用仅返回 1 或 0 的二进制步进激活函数:
我们有感知器的输出,这是一个单层神经网络,它接受一些输入并返回 1 个输出。现在模型的训练将继续通过将输出与目标进行比较、计算误差和优化权重,一遍又一遍地重复整个过程。
这是神经元的常见表示:
Deep Neural Networks
可以说所有深度学习模型都是神经网络,但并非所有神经网络都是深度学习模型。一般来说,“深度”学习适用于算法至少有 2 个隐藏层(因此总共 4 层,包括输入和输出)。
想象一下同时复制神经元过程 3 次:由于每个节点(加权和和激活函数)都返回一个值,我们将有第一个具有 3 个输出的隐藏层。
现在让我们再次使用这 3 个输出作为第二个隐藏层的输入,它返回 3 个新数字。最后,我们将添加一个输出层(仅 1 个节点)以获得我们模型的最终预测。
请记住,这些层可以具有不同数量的神经元和不同的激活函数,并且在每个节点中,训练权重以优化最终结果。这就是为什么添加的层数越多,可训练参数的数量就越大。
现在您可以查看神经网络的全貌:
请注意,为了尽可能简单,我没有提到业务可能不感兴趣的某些细节,但数据科学家绝对应该知道。尤其:
- 偏差:在每个神经元内部,输入和权重的线性组合也包含一个偏差,类似于线性方程中的常数,因此神经元的完整公式为
f( Σ(Xi * Wi ) + 偏差 )
- 反向传播:在训练期间,模型通过将误差传播回节点并更新参数(权重和偏差)来学习以最小化损失。
- 梯度下降:用于训练神经网络的优化算法,通过在最陡下降方向上重复步骤来找到损失函数的局部最小值。
Model Design
使用 TensorFlow 构建神经网络的最简单方法是使用 Keras 的 Sequential 类。让我们用它来制作我们之前示例中的感知器,因此是一个只有一个 Dense 层的模型。它是最基本的层,因为它将所有输入提供给所有神经元,每个神经元提供一个输出。
model = models.Sequential(name="Perceptron", layers=[ layers.Dense( #a fully connected layer
name="dense",
input_dim=3, #with 3 features as the input
units=1, #and 1 node because we want 1 output
activation='linear' #f(x)=x
)
])
model.summary()
摘要函数提供结构和大小的快照(根据要训练的参数)。在这种情况下,我们只有 4 个(3 个权重和 1 个偏差),所以它非常精简。
如果您想使用 Keras 中尚未包含的激活函数,例如我在可视化示例中展示的二进制步进函数,您必须使用原始 TensorFlow:
# define the function
import tensorflow as tfdef binary_step_activation(x):
##return 1 if x>0 else 0
return K.switch(x>0, tf.math.divide(x,x), tf.math.multiply(x,0))
# build the model
model = models.Sequential(name="Perceptron", layers=[
layers.Dense(
name="dense",
input_dim=3,
units=1,
activation=binary_step_activation
)
])
现在让我们尝试从感知器转移到深度神经网络。可能你会问自己一些问题:
- 几层?正确的答案是“尝试不同的变体,看看有什么用”。我通常使用带有 Dropout 的 2 个 Dense 隐藏层,这是一种通过将输入随机设置为 0 来减少过度拟合的技术。隐藏层对于克服数据的非线性很有用,所以如果你不需要非线性,那么你可以避免隐藏层。隐藏层太多会导致过拟合。
- 有多少个神经元?隐藏神经元的数量应该在输入层的大小和输出层的大小之间。我的经验法则是(输入数量 + 1 个输出)/2。
- 什么激活函数?有很多,我们不能说一个绝对更好。不管怎样,最常用的是 ReLU,一个分段线性函数,只有当输出为正时才返回,主要用于隐藏层。此外,输出层必须具有与预期输出兼容的激活。例如,线性函数适用于回归问题,而 Sigmoid 经常用于分类。
我将假设一个包含 N 个特征和 1 个二进制目标变量的输入数据集(很可能是一个分类用例)。
n_features = 10model = models.Sequential(name="DeepNN", layers=[
### hidden layer 1
layers.Dense(name="h1", input_dim=n_features,
units=int(round((n_features+1)/2)),
activation='relu'),
layers.Dropout(name="drop1", rate=0.2),
### hidden layer 2
layers.Dense(name="h2", units=int(round((n_features+1)/4)),
activation='relu'),
layers.Dropout(name="drop2", rate=0.2),
### layer output
layers.Dense(name="output", units=1, activation='sigmoid')
])
model.summary()
请注意,顺序类并不是使用 Keras 构建神经网络的唯一方法。 Model 类提供了更多的灵活性和对层的控制,它可用于构建具有多个输入/输出的更复杂的模型。有两个主要区别:
- 需要指定输入层,而在 Sequential 类中,它隐含在第一个 Dense 层的输入维度中。
- 这些层像对象一样保存,并且可以应用于其他层的输出,例如: output = layer(…)(input)
这是您可以使用 Model 类来构建我们的感知器和 DeepNN 的方法:
# Perceptron
inputs = layers.Input(name="input", shape=(3,))
outputs = layers.Dense(name="output", units=1,
activation='linear')(inputs)
model = models.Model(inputs=inputs, outputs=outputs,
name="Perceptron")
# DeepNN
### layer input
inputs = layers.Input(name="input", shape=(n_features,))### hidden layer 1
h1 = layers.Dense(name="h1", units=int(round((n_features+1)/2)), activation='relu')(inputs)
h1 = layers.Dropout(name="drop1", rate=0.2)(h1)### hidden layer 2
h2 = layers.Dense(name="h2", units=int(round((n_features+1)/4)), activation='relu')(h1)
h2 = layers.Dropout(name="drop2", rate=0.2)(h2)### layer output
outputs = layers.Dense(name="output", units=1, activation='sigmoid')(h2)model = models.Model(inputs=inputs, outputs=outputs, name="DeepNN")
可以随时检查模型摘要中的参数数量是否与 Sequential 中的参数数量相同。
Visualization
请记住,我们是在向企业讲述故事,而可视化是我们最好的盟友。我准备了一个函数来从其 TensorFlow 模型中绘制人工神经网络的结构,这是完整的代码:
让我们在我们的 2 个模型上尝试一下,首先是感知器:
visualize_nn(model, description=True, figsize=(10,8))
然后是深度神经网络:
TensorFlow 还提供了一个绘制模型结构的工具,您可能希望将它用于具有更复杂层的更复杂的神经网络(CNN、RNN 等)。有时设置起来有点棘手,如果您有问题,这篇文章可能会有所帮助。[0]
utils.plot_model(model, to_file='model.png', show_shapes=True, show_layer_names=True)
这会将这张图片保存在您的笔记本电脑上,所以如果您只想在笔记本上绘制它,您可以运行以下命令来删除该文件:
import os
os.remove('model.png')
Train & Test
最后,是时候训练我们的深度学习模型了。为了让它运行,我们必须“编译”,或者换句话说,我们需要定义 Optimizer、Loss 函数和 Metrics。我通常使用 Adam 优化器,一种用于梯度下降的替换优化算法(自适应优化器中最好的)。其他参数取决于用例。
在(二元)分类问题中,您应该使用(二元)交叉熵损失,它将每个预测概率与实际类输出进行比较。至于指标,我喜欢同时监控 Accuracy 和 F1-score,这是一个结合了 Precision 和 Recall 的指标(后者必须实现,因为它尚未包含在 TensorFlow 中)。
# define metrics
def Recall(y_true, y_pred):
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
recall = true_positives / (possible_positives + K.epsilon())
return recall
def Precision(y_true, y_pred):
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
precision = true_positives / (predicted_positives + K.epsilon())
return precision
def F1(y_true, y_pred):
precision = Precision(y_true, y_pred)
recall = Recall(y_true, y_pred)
return 2*((precision*recall)/(precision+recall+K.epsilon()))
# compile the neural network
model.compile(optimizer='adam', loss='binary_crossentropy',
metrics=['accuracy',F1])
另一方面,在回归问题中,我通常将 MAE 设置为损失,将 R-squared 设置为度量。
# define metrics
def R2(y, y_hat):
ss_res = K.sum(K.square(y - y_hat))
ss_tot = K.sum(K.square(y - K.mean(y)))
return ( 1 - ss_res/(ss_tot + K.epsilon()) )
# compile the neural network
model.compile(optimizer='adam', loss='mean_absolute_error',
metrics=[R2])
在开始训练之前,我们还需要确定 Epochs 和 Batches:由于数据集可能太大而无法一次处理,因此将其拆分为批次(批次大小越大,您需要的内存空间越多)。反向传播和随之而来的参数更新发生在每批。一个时期是对整个训练集的一次遍历。因此,如果您有 100 个观察值且批次大小为 20,则需要 5 个批次才能完成 1 个 epoch。批量大小应该是 2 的倍数(常见:32、64、128、256),因为计算机通常以 2 的幂来组织内存。我倾向于从 100 个 epoch 开始,批量大小为 32。
在训练期间,我们希望看到指标的改进和损失逐个时期减少。此外,最好保留一部分数据(20%-30%)用于验证。换句话说,该模型将分离这部分数据,以在训练之外的每个 epoch 结束时评估损失和指标。
假设您将数据准备好放入一些 X 和 y 数组中(如果没有,您可以简单地生成随机数据,例如
import numpy as npX = np.random.rand(1000,10)
y = np.random.choice([1,0], size=1000)
),您可以按如下方式启动和可视化培训:
# train/validation
training = model.fit(x=X, y=y, batch_size=32, epochs=100, shuffle=True, verbose=0, validation_split=0.3)
# plot
metrics = [k for k in training.history.keys() if ("loss" not in k) and ("val" not in k)]
fig, ax = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(15,3))
## training
ax[0].set(title="Training")
ax11 = ax[0].twinx()
ax[0].plot(training.history['loss'], color='black') ax[0].set_xlabel('Epochs')
ax[0].set_ylabel('Loss', color='black')
for metric in metrics:
ax11.plot(training.history[metric], label=metric) ax11.set_ylabel("Score", color='steelblue')
ax11.legend()
## validation
ax[1].set(title="Validation")
ax22 = ax[1].twinx()
ax[1].plot(training.history['val_loss'], color='black') ax[1].set_xlabel('Epochs')
ax[1].set_ylabel('Loss', color='black')
for metric in metrics:
ax22.plot(training.history['val_'+metric], label=metric) ax22.set_ylabel("Score", color="steelblue")
plt.show()
这些图取自两个实际用例,它们将标准机器学习算法与神经网络(每个图像下的链接)进行了比较。
Explainability
我们训练和测试了我们的模型,但我们仍然没有让企业相信结果……我们能做什么?很简单,我们构建了一个解释器来表明我们的深度学习模型不是一个黑盒子。
我发现 Shap 与神经网络配合得非常好:对于每个预测,它都能够估计每个特征对模型预测值的贡献。基本上,它回答了“为什么模型说这是 1 而不是 0?”的问题。[0]
您可以使用以下代码:
'''
Use shap to build an a explainer.
:parameter
:param model: model instance (after fitting)
:param X_names: list
:param X_instance: array of size n x 1 (n,)
:param X_train: array - if None the model is simple machine learning, if not None then it's a deep learning model
:param task: string - "classification", "regression"
:param top: num - top features to display
:return
dtf with explanations
'''
def explainer_shap(model, X_names, X_instance, X_train=None, task="classification", top=10):
## create explainer
### machine learning
if X_train is None:
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_instance)
### deep learning
else:
explainer = shap.DeepExplainer(model, data=X_train[:100])
shap_values = explainer.shap_values(X_instance.reshape(1,-1))[0].reshape(-1)
## plot
### classification
if task == "classification":
shap.decision_plot(explainer.expected_value, shap_values, link='logit', feature_order='importance',
features=X_instance, feature_names=X_names, feature_display_range=slice(-1,-top-1,-1))
### regression
else:
shap.waterfall_plot(explainer.expected_value[0], shap_values,
features=X_instance, feature_names=X_names, max_display=top)
请注意,您也可以在其他机器学习模型(即线性回归、随机森林)上使用此功能,而不仅仅是神经网络。从代码中可以看出,如果 X_train 参数保持为 None,我的函数假定它不是深度学习。
让我们在分类和回归示例上进行测试:
i = 1explainer_shap(model,
X_names=list_feature_names,
X_instance=X[i],
X_train=X,
task="classification", #task="regression"
top=10)
Conclusion
本文是一个教程,展示了如何设计和构建人工神经网络,无论是深度还是非深度。我逐步分解了单个神经元内部发生的事情,更普遍地分解了层内部发生的事情。我保持故事简单,就好像我们正在向企业解释深度学习,使用大量的可视化。
在教程的第二部分,我们使用 TensorFlow 创建了一些神经网络,从感知机到更复杂的网络。然后,我们训练了深度学习模型并评估了它对分类和回归用例的可解释性。
我希望你喜欢它!如有问题和反馈,请随时与我联系,或者只是分享您有趣的项目。
👉 Let’s Connect 👈[0]
本文是使用 Python 进行机器学习系列的一部分,另请参阅:
文章出处登录后可见!