文中讲解到的板块和对应的实现Python代码,可以很容易地迁移应用于其他类似情况(复制、粘贴、运行),我们对代码做了详尽的注释讲解。
全文的总体内容结构包括:
本篇内容使用到的是TensorFlow工具库。
我们先在终端通过pipinstall命令安装TensorFlow。
pipinstalltensorflow现在我们可以在Notebook上导入TensorFlowKeras并开始编码:
人工神经网络由若干层组成(每一层有独立的输入和输出维度)。这些层可以分组为:
最简单的神经网络形式是感知器,一个只有一层的模型,与线性回归/逻辑回归模型非常相似。
举个例子:假设我们有一个包含N行、3个特征和1个目标变量(二分类,取值0或1)的数据集,如下图所示:
实际上,数据在输入神经网络之前应该进行幅度缩放,我们这里举例的输入数据直接用了0-1之间的值。类似其他机器学习模型,我们使用输入X去预测输出y:
而当我们提到『训练模型』时,我们指的是寻找最佳参数,使得模型预测的结果能最准确地预估目标值。
这里的最佳参数,在不同的情形下,有不同的解释:
我们有一些权重初始化方法,在这里我们采用最简单的随机初始化,然后随着学习的进行调整优化参数。如下图,我们将权重w全部初始化为1:
接下来我们要进行一个简单的计算来对结果进行预估,下面的操作类似于单个神经网络的计算,f(WX+b),其中f函数叫做激活函数。
激活函数是非线性的映射函数,使得神经网络具备强大的非线性拟合学习能力,如下是一些激活函数示意图(当然,实际我们可能会更多地使用ReLU等激活函数)。
加入我们采用上面的阶跃激活函数,那简单的计算过程如下:
我们得到了感知器的输出,这是一个单层神经网络,它接受一些输入并返回1个输出。现在模型的训练将继续通过将输出与目标进行比较,计算误差并优化权重,一遍又一遍地重复整个过程。
总结一下,这就是最简单的神经元,简化的结构表示如下:
可以说所有深度学习模型都是神经网络,但并非所有神经网络都是深度学习模型。一般来说,『深度』学习适用于算法至少有2个隐藏层(因此总共4层,包括输入和输出)。
想象一下在中间层添加3个和刚才一样的神经元:由于每个节点(加权和和激活函数)都返回一个值,我们将得到具有3个输出的隐藏层。
接下来我们使用这3个输出作为第2个隐藏层的输入,第2个隐藏层也同样计算得到3个结果值。最后,我们将添加一个输出层(仅1个节点),用它的结果作为我们模型的最终预测。如下图所示
注意,这些中间层可以具有不同数量的神经元,使用不同的激活函数。每个神经元计算都会有对应的权重,因此添加的层数越多,可训练参数的数量就越大。
完整的神经网络全貌如下图所示:
我们刚才是以口语化的方式来叙述整个神经网络的结构和计算过程的,但实际有一些细节点,包括:
我们使用TensorFlow的highlevelAPI(也就是tensorflow.keras)来快速搭建神经网络。
我们先搭建刚才提到的最简单的感知器结果,它是一个只有一个Dense层的模型。Dense层是最基本的层结构,是一个全连接的结构。
要说明一点的是,如果我们这里要使用阶跃函数作为激活函数,我们需要自己定义(目前Tensorflow中的激活函数不包含这个我们临时设置的函数)
importtensorflowastf#定义激活函数defbinary_step_activation(x):##如果x>0返回1否则返回0returnK.switch(x>0,tf.math.divide(x,x),tf.math.multiply(x,0))#构建模型model=models.Sequential(name="Perceptron",layers=[layers.Dense(name="dense",input_dim=3,units=1,activation=binary_step_activation)])如果我们从感知器转延展到深度神经网络,大家可能会冒出来一些问题,比如:
应该设置多少层?
下图是层数和学习能力的一个示意图。
应该设定多少个神经元?
选用什么激活函数?
我们来解决一个二分类问题,它有N个输入特征和1个二进制目标变量。
我们用函数式方式重写上面的网络,代码如下:
完整的代码如下:
当然,TensorFlow本身也提供了一个绘制模型结构的方法,它不是像上述示例图一样的简单形式呈现,而是输出更多的模型层次信息,下面是我们对深度模型调用plot_model的结果。
下一步是训练我们前面构建的深度学习模型。在tensorflow.keras中,我们需要先对模型『编译』,或者换句话说,我们需要定义训练过程中的一些细节,比如优化器Optimizer、损失函数Loss和评估准则Metrics。其中:
详细的编译代码如下:
#定义评估准则defRecall(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())returnrecalldefPrecision(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())returnprecisiondefF1(y_true,y_pred):precision=Precision(y_true,y_pred)recall=Recall(y_true,y_pred)return2*((precision*recall)/(precision+recall+K.epsilon()))#编译神经网络model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy',F1])我们当前是分类问题,如果是回归问题,我们可以选用MAE为损失,将R方作为度量。参考代码如下:
#定义R方评估准则defR2(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()))#编译神经网络model.compile(optimizer='adam',loss='mean_absolute_error',metrics=[R2])神经网络的训练,大部分时候,不是一次性把数据都送入模型学习的(因为数据量非常大,通常GPU不足以容纳这种规模的数据,同时全量数据也容易陷入局部最低点)
我们通常会采用一个批次一个批次数据训练的方式,因此在开始训练之前,我们还需要确定Epochs和Batches:其中Epochs代表全量数据迭代的次数,Batches代表单个批次数据样本的数量。
总的数据会拆分为若干批次(每个batch的样本数量越大,您需要的内存空间越多),反向传播和参数更在每批数据上都会进行。一个Epoch是对整个训练集的一次遍历。
如果我们手头有100个样本且batch大小为20,则需要5个batch才能完成1个epoch。batch大小尽量选择为2的倍数(常见:32、64、128、256),因为计算机通常以2的幂来组织内存。
在训练过程中,理想的状态是随着一轮一轮的数据迭代,评估指标在不断改进,损失在逐步减少。不过这个结果只表明在训练集数据上我们在学习,但在新数据上是否有同样的效果并不好确定。因此我们会保留一部分数据(20%左右)用于验证评估。
我们用代码来做说明,我们在这里简单地生成随机数据构建特征数据X和标签数据y,例如
importnumpyasnpX=np.random.rand(1000,10)y=np.random.choice([1,0],size=1000)那我们可以基于下述方式对数据进行训练和评估
#训练和评估training=model.fit(x=X,y=y,batch_size=32,epochs=100,shuffle=True,verbose=0,validation_split=0.2)#绘制评估指标metrics=[kforkintraining.history.keys()if("loss"notink)and("val"notink)]fig,ax=plt.subplots(nrows=1,ncols=2,sharey=True,figsize=(15,3))#训练阶段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')formetricinmetrics:ax11.plot(training.history[metric],label=metric)ax11.set_ylabel("Score",color='steelblue')ax11.legend()#验证集评估阶段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')formetricinmetrics:ax22.plot(training.history['val_'+metric],label=metric)ax22.set_ylabel("Score",color="steelblue")plt.show()得到的结果图如下所示(下2幅图分别为分类和回归场景下的训练集与验证集的loss和评估准则指标):
实际生产过程中,神经网络效果可能很好,但我们实际是不太方向直接把它当做一个黑盒来用的,我们希望对模型做一些可解释性分析,能部分地理解我们的模型。
具体说来,对于每个样本的预测,我们结合shap都能够估计每个特征对模型预测结果的贡献,进而部分解释问模型的问题『为什么预测这是1而不是0?』(二分类场景)。
参考代码如下:
上图中,在房价预估的回归问题中,影响最大的因素是房屋的面积。