隐藏单元的设计原则(代码篇)|机器学习你会遇到的“坑”
在上一节的代码篇中,我们已经对keras的功能做了简单的介绍,我们已经可以很快的搭建出一个简单前馈神经网络,但上一节的目的主要是让大家更快的熟悉keras的基本功能,并未将模型应用到具体的数据上,我们本文会利用统计学习的一些数据,将神经网络与统计学习的一些模型做简单粗糙的对比。
首先,我们可以将神经网络应用在《过拟合问题》中使用的糖尿病数据上,它是具备10个特征的回归数据,我们可以用简单的线性回归,ridge regression, LASSO,bayesian regression,SVR等模型去学习,很难确定这些方法的效果最好可以达到多少,因为特征提取加上学习器内部的参数,我们会面临非常庞大的组合问题,所以要准确对比是较为繁琐的一项工程,在这里我们只做粗略的对比,我们引用《如何进行特征选择》一节中提到的包裹法的结果:
from sklearn.feature_selection import RFECV
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold
data=datasets.load_diabetes()
X=data['data']
y=data['target']
lr=LinearRegression()
scorer='neg_mean_squared_error'
rfecv = RFECV(estimator=lr, step=1, cv=KFold(5),
scoring=scorer)
rfecv.fit(X, y)
sns.set(style='darkgrid')
plt.xlabel("Number of features selected")
plt.ylabel("Cross validation score (MSE)")
plt.plot(range(1, len(rfecv.grid_scores_) + 1), -rfecv.grid_scores_,'r',label="Optimal number of features : %d" % rfecv.n_features_)
plt.legend()
plt.show()
如图,包裹法的学习器为简单线性回归,在对数据进行递归剔除的时候,发现特征为6的时候效果最好,MSE为2946.88。
我们为此类回归数据构建简单的一个包含两个隐层的神经网络,并将其结构打印出来:
from keras import initializers
from keras.models import Model
from keras.layers import Input,Dense,Activation
rn=initializers.RandomNormal(mean=0,stddev=1,seed=42)
inputs=Input(shape=(10,))
x=Dense(128,kernel_initializer=rn)(inputs)
x=Activation('sigmoid')(x)
x=Dense(32,kernel_initializer=rn)(x)
x=Activation('sigmoid')(x)
out=Dense(1,kernel_initializer=rn)(x)
out=Activation('linear')(out)
model=Model(inputs=inputs,outputs=out)
print(model.summary())
______________________________________________________
Layer (type) Output Shape Param #
======================================================
input_25 (InputLayer) (None, 10) 0
______________________________________________________
dense_56 (Dense) (None, 128) 1408
______________________________________________________
activation_52 (Activation) (None, 128) 0
______________________________________________________
dense_57 (Dense) (None, 32) 4128
______________________________________________________
activation_53 (Activation) (None, 32) 0
______________________________________________________
dense_58 (Dense) (None, 1) 33
______________________________________________________
activation_54 (Activation) (None, 1) 0
======================================================
Total params: 5,569
Trainable params: 5,569
Non-trainable params: 0
______________________________________________________
根据打印结果,一共有5569个参数,这里面涉及到参数数量的计算方法,熟悉神经网络结构的前提下,我们可以飞快的计算出,10*128+128+128*32+32+32*1+1=5569。
其中,我们在隐层使用了sigmoid函数作为激活函数,第一层有128个神经元,第二层有32个神经元,神经元数目的设计并没有什么特殊的道理。输出层则是一个神经元,使用了线性单元作为激活函数,本质上就是未采用激活函数。接下来,我们开始编译并训练模型:
from keras import losses,optimizers,metrics
loss=losses.mean_squared_error
performance=metrics.mean_squared_error
optimizer=optimizers.Adagrad()
model.compile(loss=loss,optimizer=optimizer,metrics=[performance])
his=model.fit(X,Y,batch_size=128,epochs=50,verbose=1,validation_split=0.3,shuffle=True)
我们使用MSE作为Loss和性能度量,Adagrad作为优化算法,同时:
- 设置batch为128,代表着每个iteration利用128个样本来更新梯度;
- 设置validation_split为0.3,代表着我们在训练开始前就划分出了0.3的样本作为验证集;
- 设置epochs为50,表示我们会对整个数据集进行50次迭代;
- 设置verbose为1,表示显示更新日志,将会出现:
Train on 309 samples, validate on 133 samples
Epoch 1/5
309/309 [==============================] - 0s 1ms/step - loss: 27615.7395 - mean_squared_error: 27615.7395 - val_loss: 29259.6711 - val_mean_squared_error: 29259.6711
Epoch 2/5
309/309 [==============================] - 0s 38us/step - loss: 26909.9610 - mean_squared_error: 26909.9610 - val_loss: 28663.5950 - val_mean_squared_error: 28663.5950
Epoch 3/5
309/309 [==============================] - 0s 30us/step - loss: 26295.9708 - mean_squared_error: 26295.9708 - val_loss: 27765.8976 - val_mean_squared_error: 27765.8976
Epoch 4/5
309/309 [==============================] - 0s 35us/step - loss: 25396.5153 - mean_squared_error: 25396.5153 - val_loss: 26754.5670 - val_mean_squared_error: 26754.5670
Epoch 5/5
309/309 [==============================] - 0s 42us/step - loss: 24493.1804 - mean_squared_error: 24493.1804 - val_loss: 25994.9439 - val_mean_squared_error: 25994.9439
.......
这里,只截取前五次的结果,因为数据量少,每个epochs是瞬间完成的,我们往往看不到进度条,但每一步我们都会获得训练集上的Loss和度量以及测试集上的Loss和度量。
我们将每一步的信息保存到了his变量中,就可以知道训练过程中Loss和性能度量的变化,在这个例子中,Loss和性能度量是一样的:
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style='darkgrid')
plt.plot(range(50),his.history['val_loss'],label='val_loss')
plt.plot(range(50),his.history['loss'],label='trian_loss')
plt.title('ANN Loss')
plt.legend()
plt.show()
如图,在50次对整体数据的迭代中,可以发现训练集和测试集的Loss下降都非常快。
特别需要注意的是,从图中还可以看出,这样的Loss好像并没有收敛的趋势,这可能代表着训练的失败。我们一方面可以设置epochs来观察其后续是否会收敛,但这样做很可能是徒劳的,因为这个神经网络参数并不多,也不深,优化起来是非常容易的,没有很快的收敛就是失败。另一方面,根据《理解梯度下降》中的知识,我们需要改变我们的优化算法,使用更简单的添加动量的随机梯度下降,而非自适应学习率算法,因为Adagrad的缺点就是很可能在一开始学习率降得太低,使得优化缓慢:
.......#其余的保持不变
optimizer=optimizers.SGD()
model.compile(loss=loss,optimizer=optimizer,metrics=[performance])
his=model.fit(X,Y,batch_size=128,epochs=50,verbose=1,validation_split=0.3,shuffle=True)
sns.set(style='darkgrid')
plt.plot(range(50),his.history['val_loss'],label='val_loss')
plt.plot(range(50),his.history['loss'],label='trian_loss')
plt.title('ANN Loss')
plt.legend()
plt.show()
如图,简单的添加动量的SGD在第2次对整体数据做迭代的时候就把Loss降低到了一个很低的水平。
此时,让我们来粗略的对比下两者,在包裹法下的简单线性回归MSE是2946,在这个神经网络中则表现为5000多,这是非常严重的一个问题。鉴于神经网络的参数太多,我们可以很容易想到其发生了过拟合(相比于线性回归),我们可以采取的策略有很多,但在这里为了不引入更多的问题,我们去掉其中含有128个神经元的隐层来达到削减神经网络规模的目的:
inputs=Input(shape=(10,))
x=Dense(32,kernel_initializer=rn)(inputs)
x=Activation('sigmoid')(x)
out=Dense(1,kernel_initializer=rn)(x)
out=Activation('linear')(out)
model=Model(inputs=inputs,outputs=out)
这样,神经网络就只包含了一个隐层,我们继续上述的步骤就可以得到:
如图,大概在30个epochs左右,我们就得到了与线性回归较为接近的MSE。
对于本文采用的数据,似乎神经网络不会超过统计学习给出的结果,哪怕是最简单的线性回归。实则不然,一方面,如果我们仔细的调节参数,神经网络应该会比简单线性回归更加出色,但是对于神经网络,调参是非常困难的,光是层数和神经元数就极难确定,更不用提改变激活函数的种类,我们下一个代码篇中展示不同的激活函数对性能的影响。另一方面,统计学习显示出了其不可替代的作用,它在很多任务中采取更少的资源,花费更少的时间就能得到一个较为满意的结果。
读芯君开扒
课堂TIPS
• 之所以采取回归数据来做示例,原因之一是我曾经收到过一个问题:为什么神经网络不做回归?是不能做回归么?我想这篇文章可以作为一个回答,神经网络当然可以做回归,只是在现在的比赛中大部分都是识别问题,也就是分类问题,对神经网络不了解的人很容易产生错误的印象。
• 我在这里使用了linear形式的输出单元,其实也就是将神经元的输入当作输出,输出单元的设计将在后续的课程中详细介绍,读者可以先行简单记住,回归问题中输出单元一般都采用linear。
• 防止神经网络过拟合的办法有很多,比如Dropout,添加正则化,参数共享,我们将会在后续的课程介绍,此处采取了减少层数的办法,在实际中这样的办法使用的并不多。
作者:唐僧不用海飞丝
如需转载,请后台留言,遵守转载规范