2.3.2 激活函数
根据万能近似原理,简单来说,神经网络有“够深的网络层”以及“至少一层带激活函数的隐藏层”,既可以拟合任意的函数。可见激活函数的重要性,它起着特征空间的非线性转换。对于激活函数选择的经验性做法:
- 对于输出层,二分类的输出层的激活函数常选择sigmoid函数,多分类选择softmax;回归任务根据输出值范围来确定使不使用激活函数。
- 对于隐藏层的激活函数通常会选择使用ReLU函数,保证学习效率。 具体可见序列文章:一文讲透神经网络的激活函数
2.3.3 权重初始化
权重参数初始化可以加速模型收敛速度,影响模型结果。常用的初始化方法有:
- uniform均匀分布初始化
- normal高斯分布初始化 需要注意的是,权重不能初始化为0,这会导致多个隐藏神经元的作用等同于1个神经元,无法收敛。
2.3.4 批标准化
batch normalization(BN)批标准化,是神经网络模型常用的一种优化方法。它的原理很简单,即是对原来的数值进行标准化处理:
batch normalization在保留输入信息的同时,消除了层与层间的分布差异,具有加快收敛,同时有类似引入噪声正则化的效果。它可应用于网络的输入层或隐藏层,当用于输入层,就是线性模型常用的特征标准化处理。
2.3.5 正则化
正则化是在以(可能)增加经验损失为代价,以降低泛化误差为目的,抑制过拟合,提高模型泛化能力的方法。经验上,对于复杂任务,深度学习模型偏好带有正则化的较复杂模型,以达到较好的学习效果。常见的正则化策略有:dropout,L1、L2、earlystop方法。具体可见序列文章:一文深层解决模型过拟合
2.3.6 选择学习目标
机器 / 深度学习通过学习到“好”的模型去决策,“好”即是机器 / 深度学习的学习目标,通常也就是预测值与目标值之间的误差尽可能的低。衡量这种误差的函数称为代价函数 (Cost Function)或者损失函数(Loss Function),更具体地说,机器 / 深度学习的目标是极大化降低损失函数。
对于不同的任务,往往也需要用不同损失函数衡量,经典的损失函数包括回归任务的均方误差损失函数及二分类任务的交叉熵损失函数等。
- 均方误差损失函数
衡量模型回归预测的误差情况,一个简单思路是用各个样本i的预测值f(x;w)减去实际值y求平方后的平均值,这也就是经典的均方误差(Mean Squared Error)损失函数。通过极小化降低均方误差损失函数,可以使得模型预测值与实际值数值差异尽量小。
- 交叉熵损失函数
衡量二分类预测模型的误差情况,常用交叉熵损失函数,使得模型预测分布尽可能与实际数据经验分布一致(最大似然估计)。
另外,还有一些针对优化难点而设计的损失函数,如Huber Loss主要用于解决回归问题中,存在奇点数据带偏模型训练的问题。Focal Loss主要解决分类问题中类别不均衡导致的模型训偏问题。
2.3.7 选择优化算法
当我们机器 / 深度学习的学习目标是极大化降低(某个)损失函数,那么如何实现这个目标呢?通常机器学习模型的损失函数较复杂,很难直接求损失函数最小的公式解。幸运的是,我们可以通过优化算法(如梯度下降、随机梯度下降、Adam等)有限次迭代优化模型参数,以尽可能降低损失函数的值,得到较优的参数值。具体可见系列文章:一文概览神经网络优化算法
对于大多数任务而言,通常可以直接先试下Adam、SGD,然后可以继续在具体任务上验证不同优化器效果。
2.3.8 模型训练及超参数调试
- 划分数据集
训练模型前,常用的HoldOut验证法(此外还有留一法、k折交叉验证等方法),把数据集分为训练集和测试集,并可再对训练集进一步细分为训练集和验证集,以方便评估模型的性能。 ① 训练集(training set):用于运行学习算法,训练模型。 ② 开发验证集(development set)用于调整模型超参数、EarlyStopping、选择特征等,以选择出合适模型。 ③ 测试集(test set)只用于评估已选择模型的性能,但不会据此改变学习算法或参数。
- 超参数调试
神经网络模型的超参数是比较多的:数据方面超参数 如验证集比例、batch size等;模型方面 如单层神经元数、网络深度、选择激活函数类型、dropout率等;学习目标方面 如选择损失函数类型,正则项惩罚系数等;优化算法方面 如选择梯度算法类型、初始学习率等。
常用的超参调试有人工经验调节、网格搜索(grid search或for循环实现)、随机搜索(random search)、贝叶斯优化(bayesian optimization)等方法,方法介绍可见系列文章:一文归纳Ai调参炼丹之法。
另外,有像Keras Tuner分布式超参数调试框架(文档见:http://keras.io/keras_tuner),集成了常用调参方法,还比较实用的。
本节代码
- 创建模型结构 结合当前房价预测任务是一个经典简单表格数据的回归预测任务。我们采用基础的全连接神经网络,隐藏层的深度一两层也就差不多。通过keras.Sequential方法来创建一个神经网络模型,并在依次添加带有批标准化的输入层,一层带有relu激活函数的k个神经元的隐藏层,并对这层隐藏层添加dropout、L1、L2正则的功能。由于回归预测数值实际范围(5~50+)直接用线性输出层,不需要加激活函数。
import numpy as np import matplotlib.pyplot as plt %matplotlib inline from tensorflow import random from keras import regularizers from keras.layers import Dense,Dropout,BatchNormalization from keras.models import Sequential, Model from keras.callbacks import EarlyStopping from sklearn.metrics import mean_squared_error np.random.seed(1) # 固定随机种子,使每次运行结果固定 random.set_seed(1) # 创建模型结构:输入层的特征维数为13;1层k个神经元的relu隐藏层;线性的输出层; for k in [5,20,50]: # 网格搜索超参数:神经元数k model = Sequential() model.add(BatchNormalization(input_dim=13)) # 输入层 批标准化 model.add(Dense(k, kernel_initializer='random_uniform', # 均匀初始化 activation='relu', # relu激活函数 kernel_regularizer=regularizers.l1_l2(l1=0.01, l2=0.01), # L1及L2 正则项 use_bias=True)) # 隐藏层 model.add(Dropout(0.1)) # dropout法 model.add(Dense(1,use_bias=True)) # 输出层
- 模型编译 设定学习目标为(最小化)回归预测损失mse,优化算法为adam
model.compile(optimizer='adam', loss='mse')
- 模型训练 我们通过传入训练集x,训练集标签y,使用fit(拟合)方法来训练模型,其中epochs为迭代次数,并通过EarlyStopping及时停止在合适的epoch,减少过拟合;batch_size为每次epoch随机采样的训练样本数目。
# 训练模型 history = model.fit(train_x, train_y, epochs=500, # 训练迭代次数 batch_size=50, # 每epoch采样的batch大小 validation_split=0.1, # 从训练集再拆分验证集,作为早停的衡量指标 callbacks=[EarlyStopping(monitor='val_loss', patience=20)], #早停法 verbose=False) # 不输出过程 print("验证集最优结果:",min(history.history['val_loss'])) model.summary() #打印模型概述信息 # 模型评估:拟合效果 plt.plot(history.history['loss'],c='blue') # 蓝色线训练集损失 plt.plot(history.history['val_loss'],c='red') # 红色线验证集损失 plt.show()
最后,这里简单采用for循环,实现类似网格搜索调整超参数,验证了隐藏层的不同神经元数目(超参数k)的效果。由验证结果来看,神经元数目为50时,损失可以达到10的较优效果(可以继续尝试模型增加深度、宽度,达到过拟合的边界应该有更好的效果)。
注:本节使用的优化方法较多(炫技ing),单纯是为展示一遍各种深度学习的优化tricks。模型并不是优化方法越多越好,效果还是要实际问题具体验证。
2.4 模型评估及优化
机器学习学习的目标是极大化降低损失函数,但这不仅仅是学习过程中对训练数据有良好的预测能力(极低的训练损失),根本上还在于要对新数据(测试集)能有很好的预测能力(泛化能力)。
- 评估模型误差的指标
评估模型的预测误差常用损失函数的大小来判断,如回归预测的均方损失。但除此之外,对于一些任务,用损失函数作为评估指标并不直观,所以像分类任务的评估还常用f1-score,可以直接展现各种类别正确分类情况。
查准率P:是指分类器预测为Positive的正确样本(TP)的个数占所有预测为Positive样本个数(TP+FP)的比例; 查全率R:是指分类器预测为Positive的正确样本(TP)的个数占所有的实际为Positive样本个数(TP+FN)的比例。 F1-score是查准率P、查全率R的调和平均:
注:如分类任务的f1-score等指标只能用于评估模型最终效果,因为作为学习目标时它们无法被高效地优化,训练优化时常用交叉熵作为其替代的分类损失函数 (surrogate loss function)。
- 评估拟合效果
评估模型拟合(学习)效果,常用欠拟合、拟合良好、过拟合来表述,通常,拟合良好的模型有更好泛化能力,在未知数据(测试集)有更好的效果。
我们可以通过训练误差及验证集误差评估模型的拟合程度。从整体训练过程来看,欠拟合时训练误差和验证集误差均较高,随着训练时间及模型复杂度的增加而下降。在到达一个拟合最优的临界点之后,训练误差下降,验证集误差上升,这个时候模型就进入了过拟合区域。
- 优化拟合效果的方法
实践中通常欠拟合不是问题,可以通过使用强特征及较复杂的模型提高学习的准确度。而解决过拟合,即如何减少泛化误差,提高泛化能力,通常才是优化模型效果的重点,常用的方法在于提高数据的质量、数量以及采用适当的正则化策略。具体可见系列文章:一文深层解决模型过拟合
本节代码
# 模型评估:拟合效果 import matplotlib.pyplot as plt plt.plot(history.history['loss'],c='blue') # 蓝色线训练集损失 plt.plot(history.history['val_loss'],c='red') # 红色线验证集损失
从训练集及验证集的损失来看,训练集、验证集损失都比较低,模型没有过拟合现象。
# 模型评估:测试集预测结果 pred_y = model.predict(test_x)[:,0] print("正确标签:",test_y) print("模型预测:",pred_y ) print("实际与预测值的差异:",mean_squared_error(test_y,pred_y )) #绘图表示 import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 设置图形大小 plt.figure(figsize=(8, 4), dpi=80) plt.plot(range(len(test_y)), test_y, ls='-.',lw=2,c='r',label='真实值') plt.plot(range(len(pred_y)), pred_y, ls='-',lw=2,c='b',label='预测值') # 绘制网格 plt.grid(alpha=0.4, linestyle=':') plt.legend() plt.xlabel('number') #设置x轴的标签文本 plt.ylabel('房价') #设置y轴的标签文本 # 展示 plt.show()
评估测试集的预测结果,其mse损失为19.7,观察测试集的实际值与预测值两者的数值曲线是比较一致的!模型预测效果较好。
2.5 模型预测结果及解释性
决策应用是机器学习最终目的,对模型预测信息加以分析解释,并应用于实际的工作领域。
对于实际工作需要注意的是,工程上是结果导向,模型在线上运行的效果直接决定模型的成败,不仅仅包括其准确程度、误差等情况,还包括其运行的速度(时间复杂度)、资源消耗程度(空间复杂度)、稳定性的综合考虑。
对于神经网络模型预测的分析解释,我们有时需要知道学习的内容,决策的过程是怎么样的(模型的可解释性)。具体可见系列文章:神经网络学习到的是什么?一个可以解释的AI模型(Explainable AI, 简称XAI)意味着运作的透明,便于人类对于对AI决策的监督及接纳,以保证算法的公平性、安全性及隐私性,从而创造更加安全可靠的应用。深度学习可解释性常用方法有:LIME、LRP、SHAP等方法。
本节代码
如下通过SHAP方法,对模型预测单个样本的结果做出解释,可见在这个样本的预测中,CRIM犯罪率为0.006、RM平均房间数为6.575对于房价是负相关的。LSTAT弱势群体人口所占比例为4.98对于房价的贡献是正相关的...,在综合这些因素后模型给出最终预测值。
import shap import tensorflow as tf # tf版本<2.0 # 模型解释性 background = test_x[np.random.choice(test_x.shape[0],100, replace=False)] explainer = shap.DeepExplainer(model,background) shap_values = explainer.shap_values(test_x) # 传入特征矩阵X,计算SHAP值 # 可视化第一个样本预测的解释 shap.force_plot(explainer.expected_value, shap_values[0,:], test_x.iloc[0,:])